From f3a8e20d7fdf97eb2ea2be3b86854488f04144fb Mon Sep 17 00:00:00 2001 From: Patrick Asmus Date: Mon, 7 Sep 2026 12:54:53 +0200 Subject: [PATCH] Release: v2.7.0 + Dokumentation ausgelagert --- .gitignore | 6 + README.md | 360 ++++----------------------- docs/befehle.md | 103 ++++++++ docs/beispiele.md | 157 ++++++++++++ docs/benachrichtigungen.md | 41 +++ docs/installation.md | 65 +++++ docs/jobs.md | 76 ++++++ docs/konfiguration.md | 66 +++++ docs/plik-upload.md | 62 +++++ docs/segment-splitting.md | 76 ++++++ docs/zeitplanung.md | 80 ++++++ install/install.sh | 19 +- jobs/templates/example-hls.job.dist | 7 + jobs/templates/example-mp3.job.dist | 7 + jobs/templates/example-rtmp.job.dist | 10 + stream-recorder.sh | 298 ++++++++++++++++++++-- 16 files changed, 1103 insertions(+), 330 deletions(-) create mode 100644 .gitignore create mode 100644 docs/befehle.md create mode 100644 docs/beispiele.md create mode 100644 docs/benachrichtigungen.md create mode 100644 docs/installation.md create mode 100644 docs/jobs.md create mode 100644 docs/konfiguration.md create mode 100644 docs/plik-upload.md create mode 100644 docs/segment-splitting.md create mode 100644 docs/zeitplanung.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2adf889 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Benutzerspezifische Konfiguration (aus .dist erstellt) +config/stream-recorder.conf + +# Job-Dateien (nur Vorlagen werden versioniert) +jobs/*.job +!jobs/templates/ diff --git a/README.md b/README.md index 54df121..318cbe2 100644 --- a/README.md +++ b/README.md @@ -19,350 +19,88 @@ · 💬 Support -

- -## Beschreibung - -**stream-recorder** ist ein Bash-basiertes Tool zur automatisierten Aufzeichnung von Streams. Es unterstützt verschiedene Stream-Typen und kann sowohl interaktiv als auch vollautomatisch genutzt werden. - -Jeder Stream wird als eigene Job-Datei definiert. Aufnahmezeiten werden direkt in der Job-Datei festgelegt - kein Cron-Wissen nötig. Ein systemd-Timer übernimmt die Zeitsteuerung automatisch. +
## Funktionen -- **Mehrere Stream-Typen:** RTMP, RTMPS, HLS (m3u8), MP3, AAC, OGG, FLAC und generische HTTP-Streams -- **Job-basiert:** Ein Stream = eine Job-Datei, inklusive Zeitplanung -- **Automatische Zeitplanung:** Aufnahmezeiten direkt in der Job-Datei konfigurieren -- **Schnellaufnahme:** Interaktiver Modus für spontane Aufnahmen (`quick`) -- **Automatische Erkennung:** Stream-Typ und Dateiformat werden anhand der URL erkannt -- **Auto-Reconnect:** Automatische Wiederverbindung bei Verbindungsabbrüchen -- **NTFY-Benachrichtigungen:** Optionale Push-Nachrichten bei Start, Stop und Fehlern -- **Logging:** Strukturiertes Logging mit konfigurierbarem Log-Level und Live-Ansicht -- **Prozessverwaltung:** Start, Stop und Status einzelner oder aller Aufnahmen -- **Einfache Installation:** Install/Uninstall-Scripts für den systemd-Service +- **Mehrere Stream-Typen** — RTMP, RTMPS, HLS (m3u8), MP3, AAC, OGG, FLAC und HTTP-Streams +- **Job-basiert** — Ein Stream = eine Job-Datei, inklusive Zeitplanung +- **Segment-Splitting** — Lange Aufnahmen automatisch in Teile zerlegen (z.B. stündlich) +- **Plik-Upload** — Aufnahmen und Segmente automatisch auf Plik hochladen +- **Zeitplanung** — Aufnahmezeiten direkt in der Job-Datei konfigurieren (kein Cron nötig) +- **Auto-Reconnect** — Automatische Wiederverbindung bei Verbindungsabbrüchen +- **Live-Status** — Laufende Aufnahmen mit Laufzeit, Dateigröße und Segment-Anzahl anzeigen +- **NTFY-Benachrichtigungen** — Push-Nachrichten bei Start, Stop, Fehler und Upload +- **Schnellaufnahme** — Interaktiver Modus für spontane Aufnahmen ## Voraussetzungen -- **Betriebssystem:** Linux (getestet unter Ubuntu/Debian) -- **Bash:** Version 4.0 oder neuer -- **ffmpeg:** Muss installiert sein (`sudo apt install ffmpeg`) -- **curl:** Optional, nur für NTFY-Benachrichtigungen (`sudo apt install curl`) +- **Linux** (getestet unter Ubuntu/Debian) +- **Bash** 4.0+ +- **ffmpeg** (`sudo apt install ffmpeg`) +- **curl** (optional, für NTFY und Plik) -## Installation +## Schnellstart ```bash -# Repository klonen +# Klonen und einrichten git clone https://git.techniverse.net/scriptos/stream-recorder.git /opt/stream-recorder cd /opt/stream-recorder - -# Konfiguration anpassen +cp config/stream-recorder.conf.dist config/stream-recorder.conf nano config/stream-recorder.conf -# Scheduler installieren (systemd-Timer) +# Scheduler installieren (optional) sudo install/install.sh -``` -Das Install-Script macht das Hauptscript ausführbar, installiert fehlende Abhängigkeiten (ffmpeg) und richtet den systemd-Timer ein, der jede Minute die Zeitpläne prüft. - -### Deinstallation - -```bash -sudo install/uninstall.sh -``` - -Entfernt den systemd-Timer. Job-Dateien, Konfiguration und Aufnahmen bleiben erhalten. - -## Konfiguration - -Die Konfigurationsdatei liegt unter `config/stream-recorder.conf`: - -| Parameter | Standard | Beschreibung | -|-----------------|------------------------------|---------------------------------------| -| `DOWNLOAD_PATH` | `/home/downloads/recordings` | Basis-Verzeichnis für Aufnahmen | -| `LOG_DIR` | `/var/log/stream-recorder` | Log-Verzeichnis | -| `LOG_LEVEL` | `INFO` | Log-Level: DEBUG, INFO, WARN, ERROR | -| `PID_DIR` | `/tmp/stream-recorder` | PID-Dateien für laufende Aufnahmen | -| `STATE_DIR` | `/var/lib/stream-recorder` | Status für bereits gestartete Zeitpläne | -| `MAX_RETRIES` | `0` | Wiederholungsversuche (0 = unbegrenzt)| -| `RETRY_DELAY` | `5` | Wartezeit zwischen Versuchen (Sek.) | -| `NTFY_URL` | (leer) | NTFY Server-URL inkl. Topic | -| `NTFY_TOKEN` | (leer) | NTFY Access-Token (optional) | -| `NTFY_EVENTS` | `error` | Benachrichtigungs-Events (s. unten) | -| `PLIK_URL` | (leer) | Plik Server-URL | -| `PLIK_API_KEY` | (leer) | Plik API-Key (optional) | -| `PLIK_TTL` | `30d` | Gültigkeitsdauer der Uploads | - -## Job-Dateien - -Jeder Stream wird durch eine `.job`-Datei im `jobs/`-Verzeichnis definiert. Vorlagen befinden sich unter `jobs/templates/`. - -### Job erstellen - -**Interaktiv (empfohlen):** - -```bash -./stream-recorder.sh create -``` - -Das Script fragt Schritt für Schritt nach URL, Name, Typ, Zeitplan und Plik-Upload und erstellt die Job-Datei automatisch. Bei aktiviertem Zeitplan wird die Aufnahmedauer wahlweise per Endzeit oder Laufzeit begrenzt — das jeweils andere wird automatisch berechnet. - -**Manuell (aus Vorlage):** - -```bash -# Vorlage kopieren -cp jobs/templates/example-rtmp.job.dist jobs/mein-stream.job - -# Anpassen -nano jobs/mein-stream.job -``` - -### Stream-Parameter - -| Parameter | Pflicht | Beschreibung | -|--------------------|---------|-----------------------------------------------------| -| `STREAM_NAME` | Ja | Name des Streams (wird für Dateinamen verwendet) | -| `STREAM_URL` | Ja | URL des Streams | -| `STREAM_TYPE` | Nein | `auto`, `rtmp`, `hls`, `mp3`, `aac`, `ogg`, `flac` | -| `OUTPUT_FORMAT` | Nein | Dateiendung (z.B. `mp4`, `mp3`) - sonst automatisch | -| `MAX_DURATION` | Nein | Aufnahmedauer in Sekunden (wird bei Zeitplanung automatisch aus Endzeit oder Laufzeit berechnet) | -| `EXTRA_FFMPEG_ARGS`| Nein | Zusätzliche ffmpeg-Parameter | -| `PLIK_ENABLED` | Nein | Plik-Upload aktivieren (`true`/`false`) | -| `DELETE_AFTER_UPLOAD` | Nein | Lokale Datei nach erfolgreichem Upload löschen (`true`/`false`) | - -### Zeitplan-Parameter - -| Parameter | Standard | Beschreibung | -|--------------------|----------|-------------------------------------------------| -| `SCHEDULE_ENABLED` | `false` | Zeitplan aktivieren (`true`/`false`) | -| `SCHEDULE_ONCE` | `false` | Einmaliger Termin statt wöchentlichem Zeitplan | -| `SCHEDULE_DATE` | (leer) | Datum für einmalige Termine (`YYYY-MM-DD`) | -| `SCHEDULE_DAYS` | `*` | Aufnahmetage für wöchentliche Zeitpläne | -| `SCHEDULE_START` | (leer) | Startzeit im Format `HH:MM` | -| `SCHEDULE_STOP` | (leer) | Endzeit im Format `HH:MM` (leer = kein Auto-Stop)| - -**Tage-Formate:** - -| Format | Bedeutung | -|--------------|----------------------------| -| `*` | Täglich | -| `Mo-Fr` | Montag bis Freitag | -| `Sa,So` | Samstag und Sonntag | -| `Mo,Mi,Fr` | Einzelne Tage | -| `Mo-Fr,So` | Kombiniert | - -Die Tagesangaben funktionieren auf Deutsch (Mo, Di, Mi, Do, Fr, Sa, So) und Englisch (Mon, Tue, Wed, Thu, Fri, Sat, Sun). - -Ohne `SCHEDULE_ONCE=true` sind Tagesangaben wöchentlich/wiederkehrend. Ein Job mit `SCHEDULE_DAYS="Mi"` und `SCHEDULE_START="21:00"` läuft also jeden Mittwoch. - -### Beispiel: Tägliche RTMP-Aufnahme von 20:00 bis 22:00 - -```bash -STREAM_NAME="Abend-Livestream" -STREAM_URL="rtmp://example.com/live/stream-key" -SCHEDULE_ENABLED=true -SCHEDULE_DAYS="*" -SCHEDULE_START="20:00" -SCHEDULE_STOP="22:00" -``` - -### Beispiel: Radio-Mitschnitt werktags um 18:00, 1 Stunde - -```bash -STREAM_NAME="Mein-Radiosender" -STREAM_URL="https://example.com/stream.mp3" -MAX_DURATION="1h" -SCHEDULE_ENABLED=true -SCHEDULE_ONCE=false -SCHEDULE_DAYS="Mo-Fr" -SCHEDULE_START="18:00" -``` - -### Beispiel: Einmalige Aufnahme am 09.09.2026 um 21:00, 30 Minuten - -```bash -STREAM_NAME="Einmaliger-Mitschnitt" -STREAM_URL="https://example.com/stream.mp3" -MAX_DURATION="30m" -SCHEDULE_ENABLED=true -SCHEDULE_ONCE=true -SCHEDULE_DATE="2026-09-09" -SCHEDULE_START="21:00" -SCHEDULE_STOP="" -``` - -## Verwendung - -### Befehle - -```bash -# Einzelnen Stream aufzeichnen -./stream-recorder.sh record mein-stream - -# Im Vordergrund aufzeichnen, z.B. für Tests -./stream-recorder.sh --foreground record mein-stream - -# Alle Jobs gleichzeitig aufzeichnen -./stream-recorder.sh record-all - -# Neuen Job interaktiv erstellen +# Ersten Job erstellen ./stream-recorder.sh create -# Interaktive Schnellaufnahme (URL direkt eingeben) -./stream-recorder.sh quick - -# Vorhandene Aufnahme erneut nach Plik hochladen -./stream-recorder.sh upload /pfad/zur/aufnahme.mp4 "Stream-Name" - -# Aufnahme mit ausführlicher Ausgabe -./stream-recorder.sh -V record mein-stream - -# Verfügbare Jobs und Zeitpläne anzeigen -./stream-recorder.sh list - -# Laufende Aufnahmen und Scheduler-Status anzeigen -./stream-recorder.sh status - -# Log-Ausgabe live verfolgen -./stream-recorder.sh logs - -# Einzelne Aufnahme stoppen -./stream-recorder.sh stop mein-stream - -# Alle Aufnahmen stoppen -./stream-recorder.sh stop-all -``` - -### Schnellaufnahme - -Mit dem `quick`-Befehl kann ein beliebiger Stream ohne Job-Datei aufgezeichnet werden: - -```bash +# Oder direkt loslegen ./stream-recorder.sh quick ``` -Das Script fragt interaktiv nach URL, Name, Typ und Dauer. Die Dauer kann in verschiedenen Formaten angegeben werden: `3600` (Sekunden), `1h`, `30m`, `1h30m`. - -### Job-Angabe - -Der `record`-Befehl akzeptiert verschiedene Formate: +## Befehle ```bash -./stream-recorder.sh record jobs/mein-stream.job # Vollständiger Pfad -./stream-recorder.sh record mein-stream.job # Nur Dateiname -./stream-recorder.sh record mein-stream # Nur Job-Name +./stream-recorder.sh record # Aufnahme starten +./stream-recorder.sh stop # Aufnahme stoppen +./stream-recorder.sh status # Laufende Aufnahmen anzeigen +./stream-recorder.sh list # Alle Jobs anzeigen +./stream-recorder.sh create # Neuen Job erstellen +./stream-recorder.sh quick # Schnellaufnahme +./stream-recorder.sh logs # Logs live verfolgen ``` -## Zeitplanung +## Dokumentation -Die Zeitplanung wird direkt in der Job-Datei konfiguriert - kein Cron nötig. - -### So funktioniert's - -1. **Job-Datei erstellen** und `SCHEDULE_*`-Parameter setzen -2. **Scheduler installieren** mit `sudo install/install.sh` (einmalig) -3. Der systemd-Timer prüft **jede Minute** alle Jobs und startet/stoppt Aufnahmen automatisch - -### Verhalten - -- **Start + Stop gesetzt:** Aufnahme läuft im angegebenen Zeitfenster -- **Nur Start gesetzt:** Aufnahme startet zur angegebenen Zeit, läuft bis `MAX_DURATION` oder manueller Stop -- **Einmalig:** Mit `SCHEDULE_ONCE=true` und `SCHEDULE_DATE="YYYY-MM-DD"` startet der Job nur an diesem Datum -- **Manuell gestartete Aufnahmen** werden vom Scheduler nicht beeinflusst -- **Nach Neustart** des Servers erkennt der Scheduler automatisch, welche Aufnahmen laufen sollten - -### NTFY-Benachrichtigungen - -Push-Nachrichten via [ntfy](https://ntfy.sh) (selbst gehostet oder öffentlich). In der Konfigurationsdatei: - -```bash -NTFY_URL="https://ntfy.sh/mein-stream-topic" -NTFY_TOKEN="" # Optional, nur bei geschützten Topics -NTFY_EVENTS="start,stop,error" # Kommasepariert: start, stop, error -``` - -| Event | Auslöser | Priorität | -|---------|-----------------------------------------------------|-----------| -| `start` | Aufnahme wurde gestartet | Normal | -| `stop` | Aufnahme wurde beendet (regulär oder manuell) | Normal | -| `error` | Verbindung verloren oder max. Versuche erreicht | Hoch | -| `upload` | Datei wurde auf Plik hochgeladen (mit Download-Link)| Normal | - -## Auto-Reconnect - -Bei Verbindungsabbrüchen versucht das Script automatisch, die Verbindung wiederherzustellen: - -- `MAX_RETRIES=0` - Unbegrenzte Wiederverbindungsversuche (Standard) -- `MAX_RETRIES=5` - Maximal 5 Versuche, danach Abbruch mit NTFY-Benachrichtigung -- `RETRY_DELAY=5` - 5 Sekunden Wartezeit zwischen den Versuchen - -Bei jedem Reconnect wird eine neue Datei mit aktuellem Zeitstempel erzeugt. - -## Plik-Upload - -Fertige Aufnahmen können automatisch auf einen [Plik](https://github.com/root-gg/plik)-Server hochgeladen werden. - -### Einrichtung - -1. **Plik-Server konfigurieren** in `config/stream-recorder.conf`: - -```bash -PLIK_URL="https://plik.example.com" -PLIK_API_KEY="" # Optional, nur bei geschützten Servern -PLIK_TTL="30d" # Gültigkeitsdauer: 30d, 24h, oder Sekunden -``` - -2. **Pro Job aktivieren** in der Job-Datei: - -```bash -PLIK_ENABLED=true -``` - -### Verhalten - -- Der Upload erfolgt automatisch nach jeder regulär abgeschlossenen Aufnahme -- Bei manuellem Stop oder Verbindungsabbruch wird kein Upload ausgeführt -- Der Browser-Link zur Plik-Upload-Seite wird im Log und per NTFY ausgegeben -- Die direkte Datei-URL wird zusätzlich ins Log geschrieben -- Optional wird der Link per NTFY-Benachrichtigung gesendet (Event: `upload`) -- Plik-Uploads verwenden den direkten Multipart-Upload-Endpunkt des Servers. - -### NTFY + Plik - -Um den Download-Link per Push-Nachricht zu erhalten: - -```bash -NTFY_EVENTS="start,stop,error,upload" -``` - -## Logging - -Logs werden nach `LOG_DIR` geschrieben (Standard: `/var/log/stream-recorder/`): - -- `stream-recorder.log` - Alle Operationen des Scripts -- `ffmpeg_.log` - ffmpeg-Ausgabe pro Job - -```bash -# Logs in Echtzeit verfolgen -./stream-recorder.sh logs - -# Aufnahme mit ausführlicher Ausgabe starten -./stream-recorder.sh -V record mein-stream -``` +| Thema | Beschreibung | +|-------|-------------| +| [Installation](docs/installation.md) | Installation, Updates, Deinstallation | +| [Konfiguration](docs/konfiguration.md) | Alle Konfigurationsparameter | +| [Job-Dateien](docs/jobs.md) | Jobs erstellen und verwalten | +| [Zeitplanung](docs/zeitplanung.md) | Automatische Aufnahmezeiten | +| [Segment-Splitting](docs/segment-splitting.md) | Aufnahmen in Teile zerlegen | +| [Plik-Upload](docs/plik-upload.md) | Automatischer Upload auf Plik | +| [Benachrichtigungen](docs/benachrichtigungen.md) | NTFY Push-Nachrichten | +| [Befehle](docs/befehle.md) | Alle Befehle im Detail | +| [Beispiele](docs/beispiele.md) | Praxisbeispiele und Workflows | ## Verzeichnisstruktur ``` stream-recorder/ -├── stream-recorder.sh # Hauptscript +├── stream-recorder.sh # Hauptscript ├── config/ -│ └── stream-recorder.conf # Konfiguration -├── jobs/ # Job-Definitionen -│ └── templates/ # Vorlagen +│ └── stream-recorder.conf.dist # Konfigurations-Vorlage +├── jobs/ # Job-Definitionen +│ └── templates/ # Vorlagen │ ├── example-rtmp.job.dist -│ ├── example-mp3.job.dist -│ └── example-hls.job.dist +│ ├── example-hls.job.dist +│ └── example-mp3.job.dist +├── docs/ # Dokumentation ├── install/ -│ ├── install.sh # Scheduler installieren -│ └── uninstall.sh # Scheduler deinstallieren +│ ├── install.sh # Scheduler installieren +│ └── uninstall.sh # Scheduler deinstallieren ├── README.md └── LICENSE ``` diff --git a/docs/befehle.md b/docs/befehle.md new file mode 100644 index 0000000..f2d4e1f --- /dev/null +++ b/docs/befehle.md @@ -0,0 +1,103 @@ +# Befehle + +## Übersicht + +| Befehl | Beschreibung | +|----------------------|-------------------------------------------------| +| `record ` | Einzelnen Stream aufzeichnen | +| `record-all` | Alle Jobs gleichzeitig aufzeichnen | +| `create` | Neuen Job interaktiv erstellen | +| `quick` | Schnellaufnahme (URL direkt eingeben) | +| `upload ` | Vorhandene Datei nach Plik hochladen | +| `stop ` | Aufnahme stoppen (Teilaufnahme wird hochgeladen)| +| `stop-all` | Alle Aufnahmen stoppen | +| `list` | Jobs und deren Zeitpläne anzeigen | +| `status` | Laufende Aufnahmen mit Details anzeigen | +| `scheduler` | Zeitplanung prüfen und Jobs starten/stoppen | +| `logs` | Log-Ausgabe live verfolgen | + +## Optionen + +| Option | Beschreibung | +|--------------------------|---------------------------------------| +| `-c, --config ` | Alternative Konfigurationsdatei | +| `-V, --verbose` | Ausführliche Ausgabe | +| `--foreground` | Aufnahme im Vordergrund ausführen | +| `-h, --help` | Hilfe anzeigen | +| `-v, --version` | Version anzeigen | + +## Details + +### record + +Startet eine Aufnahme im Hintergrund (Standard) oder Vordergrund (`--foreground`). + +```bash +./stream-recorder.sh record mein-stream +./stream-recorder.sh --foreground record mein-stream +./stream-recorder.sh -V record mein-stream +``` + +### create + +Erstellt interaktiv eine neue Job-Datei. Fragt nach URL, Name, Typ, Segment-Splitting, Zeitplan und Plik-Upload. + +```bash +./stream-recorder.sh create +``` + +### quick + +Schnellaufnahme ohne Job-Datei. Fragt nach URL, Name, Typ und Dauer. + +```bash +./stream-recorder.sh quick +``` + +### stop / stop-all + +Stoppt eine laufende Aufnahme. Wenn Plik aktiviert ist, wird die Teilaufnahme hochgeladen. Bei Segment-Splitting werden alle verbleibenden Segmente hochgeladen. + +```bash +./stream-recorder.sh stop mein-stream +./stream-recorder.sh stop-all +``` + +### status + +Zeigt detaillierte Informationen zu laufenden Aufnahmen: +- Stream-Name und Typ +- PID und Startmodus (manuell/geplant) +- Startzeit und bisherige Laufzeit +- Dateigröße bzw. Anzahl der Segmente und Gesamtgröße +- Scheduler-Status (systemd-Timer) + +```bash +./stream-recorder.sh status +``` + +### list + +Zeigt alle konfigurierten Jobs mit Status, Zeitplan, Segment-Einstellungen und Plik-Konfiguration. + +```bash +./stream-recorder.sh list +``` + +### logs + +Verfolgt die Log-Ausgabe in Echtzeit (`tail -f`). + +```bash +./stream-recorder.sh logs +``` + +## Logging + +Logs werden nach `LOG_DIR` geschrieben (Standard: `/var/log/stream-recorder/`): + +| Datei | Inhalt | +|----------------------------|---------------------------------| +| `stream-recorder.log` | Alle Operationen des Scripts | +| `ffmpeg_.log` | ffmpeg-Ausgabe pro Job | +| `nohup_.log` | Ausgabe im Hintergrund-Modus | diff --git a/docs/beispiele.md b/docs/beispiele.md new file mode 100644 index 0000000..1bd56a6 --- /dev/null +++ b/docs/beispiele.md @@ -0,0 +1,157 @@ +# Praxisbeispiele + +## RTMP-Livestream täglich aufzeichnen + +Tägliche Aufnahme eines Twitch-/YouTube-/RTMP-Streams von 20:00 bis 22:00 Uhr mit Plik-Upload: + +```bash +# jobs/abend-livestream.job +STREAM_NAME="Abend-Livestream" +STREAM_URL="rtmp://example.com/live/stream-key" +STREAM_TYPE="auto" +OUTPUT_FORMAT="" +MAX_DURATION="" +EXTRA_FFMPEG_ARGS="" + +SEGMENT_DURATION="" + +SCHEDULE_ENABLED=true +SCHEDULE_DAYS="*" +SCHEDULE_START="20:00" +SCHEDULE_STOP="22:00" + +PLIK_ENABLED=true +DELETE_AFTER_UPLOAD=false +``` + +## Radio-Mitschnitt werktags + +Werktags um 18:00 Uhr eine Stunde Radio mitschneiden: + +```bash +# jobs/radio-abends.job +STREAM_NAME="Radio-Abendshow" +STREAM_URL="https://example.com/stream.mp3" +STREAM_TYPE="auto" +OUTPUT_FORMAT="" +MAX_DURATION="1h" +EXTRA_FFMPEG_ARGS="" + +SEGMENT_DURATION="" + +SCHEDULE_ENABLED=true +SCHEDULE_DAYS="Mo-Fr" +SCHEDULE_START="18:00" +SCHEDULE_STOP="" + +PLIK_ENABLED=false +``` + +## 24/7-Stream mit stündlichem Upload + +Ein Überwachungsstream, der dauerhaft läuft und stündlich Segmente zu Plik hochlädt. Lokale Dateien werden nach dem Upload gelöscht: + +```bash +# jobs/ueberwachung.job +STREAM_NAME="Kamera-Front" +STREAM_URL="rtmp://192.168.1.100/live/cam1" +STREAM_TYPE="auto" +OUTPUT_FORMAT="mp4" +MAX_DURATION="" +EXTRA_FFMPEG_ARGS="" + +SEGMENT_DURATION="1h" + +SCHEDULE_ENABLED=false + +PLIK_ENABLED=true +DELETE_AFTER_UPLOAD=true +``` + +Starten mit: + +```bash +./stream-recorder.sh record ueberwachung +``` + +## Podcast-/HLS-Stream in 2-Stunden-Blöcken + +Ein langer HLS-Livestream (z.B. Konferenz) in 2-Stunden-Segmenten aufzeichnen, jedes Segment wird sofort hochgeladen: + +```bash +# jobs/konferenz.job +STREAM_NAME="Dev-Konferenz-2026" +STREAM_URL="https://example.com/live/playlist.m3u8" +STREAM_TYPE="hls" +OUTPUT_FORMAT="mp4" +MAX_DURATION="10h" +EXTRA_FFMPEG_ARGS="" + +SEGMENT_DURATION="2h" + +SCHEDULE_ENABLED=false + +PLIK_ENABLED=true +DELETE_AFTER_UPLOAD=false +``` + +## Einmalige Aufnahme zu einem bestimmten Termin + +Ein Event am 15.09.2026 um 21:00 Uhr aufnehmen, 2 Stunden: + +```bash +# jobs/einmaliges-event.job +STREAM_NAME="Einmaliges-Event" +STREAM_URL="https://example.com/live/playlist.m3u8" +STREAM_TYPE="hls" +OUTPUT_FORMAT="mp4" +MAX_DURATION="2h" +EXTRA_FFMPEG_ARGS="" + +SEGMENT_DURATION="" + +SCHEDULE_ENABLED=true +SCHEDULE_ONCE=true +SCHEDULE_DATE="2026-09-15" +SCHEDULE_START="21:00" +SCHEDULE_STOP="" + +PLIK_ENABLED=true +DELETE_AFTER_UPLOAD=false +``` + +## Schnellaufnahme ohne Job-Datei + +Für spontane Aufnahmen ohne Konfiguration: + +```bash +./stream-recorder.sh quick +``` + +Das Script fragt interaktiv nach URL, Name, Typ und Dauer. + +## Typischer Workflow + +```bash +# 1. Konfiguration erstellen (einmalig) +cp config/stream-recorder.conf.dist config/stream-recorder.conf +nano config/stream-recorder.conf + +# 2. Scheduler installieren (einmalig) +sudo install/install.sh + +# 3. Job erstellen +./stream-recorder.sh create + +# 4. Jobs anzeigen +./stream-recorder.sh list + +# 5. Status prüfen +./stream-recorder.sh status + +# 6. Logs verfolgen +./stream-recorder.sh logs + +# 7. Aufnahme stoppen (falls nötig) +./stream-recorder.sh stop mein-stream +``` diff --git a/docs/benachrichtigungen.md b/docs/benachrichtigungen.md new file mode 100644 index 0000000..83e7597 --- /dev/null +++ b/docs/benachrichtigungen.md @@ -0,0 +1,41 @@ +# NTFY-Benachrichtigungen + +Push-Nachrichten via [ntfy](https://ntfy.sh) (selbst gehostet oder öffentlich) bei wichtigen Ereignissen. + +## Einrichtung + +In `config/stream-recorder.conf`: + +```bash +NTFY_URL="https://ntfy.sh/mein-stream-topic" +NTFY_TOKEN="" # Optional, nur bei geschützten Topics +NTFY_EVENTS="start,stop,error" # Kommasepariert +``` + +## Events + +| Event | Auslöser | Priorität | +|----------|-----------------------------------------------------|-----------| +| `start` | Aufnahme wurde gestartet | Normal | +| `stop` | Aufnahme beendet (regulär oder manuell) | Normal | +| `error` | Verbindung verloren oder max. Versuche erreicht | Hoch | +| `upload` | Datei auf Plik hochgeladen (mit Download-Link) | Normal | + +## Alle Events aktivieren + +```bash +NTFY_EVENTS="start,stop,error,upload" +``` + +## Nur Fehler (Standard) + +```bash +NTFY_EVENTS="error" +``` + +## Hinweise + +- Benachrichtigungen werden asynchron im Hintergrund gesendet +- Ein fehlgeschlagener Versand blockiert die Aufnahme nicht +- curl muss installiert sein (`sudo apt install curl`) +- Ohne gesetzte `NTFY_URL` sind Benachrichtigungen komplett deaktiviert diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..2052f98 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,65 @@ +# Installation + +## Voraussetzungen + +| Komponente | Anforderung | +|------------------|------------------------------------------| +| Betriebssystem | Linux (getestet unter Ubuntu/Debian) | +| Bash | Version 4.0 oder neuer | +| ffmpeg | Pflicht (`sudo apt install ffmpeg`) | +| curl | Optional, für NTFY und Plik-Upload | + +## Installation + +```bash +# Repository klonen +git clone https://git.techniverse.net/scriptos/stream-recorder.git /opt/stream-recorder +cd /opt/stream-recorder + +# Konfiguration aus Vorlage erstellen und anpassen +cp config/stream-recorder.conf.dist config/stream-recorder.conf +nano config/stream-recorder.conf + +# Scheduler installieren (systemd-Timer) +sudo install/install.sh +``` + +Das Install-Script: +- Macht das Hauptscript ausführbar +- Installiert fehlende Abhängigkeiten (ffmpeg) +- Erstellt die Konfigurationsdatei aus der Vorlage (falls nicht vorhanden) +- Richtet einen systemd-Timer ein, der jede Minute die Zeitpläne prüft + +## Updates + +Da die Konfiguration als Kopie (`stream-recorder.conf`) vorliegt und die Vorlage (`stream-recorder.conf.dist`) getrennt gepflegt wird, gehen eigene Einstellungen bei einem `git pull` nicht verloren. + +```bash +cd /opt/stream-recorder +git pull +``` + +Nach einem Update sollte man prüfen, ob in `stream-recorder.conf.dist` neue Parameter hinzugekommen sind, und diese ggf. in die eigene `stream-recorder.conf` übernehmen. + +## Deinstallation + +```bash +sudo install/uninstall.sh +``` + +Entfernt den systemd-Timer und stoppt optional alle laufenden Aufnahmen. Job-Dateien, Konfiguration und Aufnahmen bleiben erhalten. + +## Ohne systemd + +Das Script funktioniert auch ohne Scheduler. In dem Fall muss man Aufnahmen manuell starten: + +```bash +./stream-recorder.sh record mein-stream +``` + +Oder den Scheduler manuell per Cron aufrufen: + +```bash +# crontab -e +* * * * * /opt/stream-recorder/stream-recorder.sh scheduler +``` diff --git a/docs/jobs.md b/docs/jobs.md new file mode 100644 index 0000000..95892e7 --- /dev/null +++ b/docs/jobs.md @@ -0,0 +1,76 @@ +# Job-Dateien + +Jeder Stream wird durch eine `.job`-Datei im `jobs/`-Verzeichnis definiert. Vorlagen befinden sich unter `jobs/templates/`. + +## Job erstellen + +### Interaktiv (empfohlen) + +```bash +./stream-recorder.sh create +``` + +Das Script fragt Schritt für Schritt nach URL, Name, Typ, Segment-Splitting, Zeitplan und Plik-Upload und erstellt die Job-Datei automatisch. + +### Aus Vorlage + +```bash +# Vorlage kopieren +cp jobs/templates/example-rtmp.job.dist jobs/mein-stream.job + +# Anpassen +nano jobs/mein-stream.job +``` + +Verfügbare Vorlagen: + +| Vorlage | Beschreibung | +|----------------------------|-------------------------| +| `example-rtmp.job.dist` | RTMP/RTMPS-Stream | +| `example-hls.job.dist` | HLS/m3u8-Stream | +| `example-mp3.job.dist` | HTTP-Audio (MP3/Radio) | + +## Stream-Parameter + +| Parameter | Pflicht | Beschreibung | +|----------------------|---------|-----------------------------------------------------| +| `STREAM_NAME` | Ja | Name des Streams (wird für Dateinamen verwendet) | +| `STREAM_URL` | Ja | URL des Streams | +| `STREAM_TYPE` | Nein | `auto`, `rtmp`, `hls`, `mp3`, `aac`, `ogg`, `flac` | +| `OUTPUT_FORMAT` | Nein | Dateiendung (z.B. `mp4`, `mp3`) — sonst automatisch | +| `MAX_DURATION` | Nein | Aufnahmedauer (z.B. `1h`, `30m`, `1d12h`, `3600`) | +| `EXTRA_FFMPEG_ARGS` | Nein | Zusätzliche ffmpeg-Parameter | +| `SEGMENT_DURATION` | Nein | Segment-Splitting (z.B. `1h`, `2h`) — siehe [Segment-Splitting](segment-splitting.md) | +| `PLIK_ENABLED` | Nein | Plik-Upload aktivieren (`true`/`false`) | +| `DELETE_AFTER_UPLOAD` | Nein | Lokale Datei nach Upload löschen (`true`/`false`) | + +### Dauerformate + +Die Dauer kann in verschiedenen Formaten angegeben werden: + +| Format | Bedeutung | +|----------|------------------| +| `3600` | 3600 Sekunden | +| `1h` | 1 Stunde | +| `30m` | 30 Minuten | +| `1h30m` | 90 Minuten | +| `2d` | 2 Tage | +| `1d12h` | 36 Stunden | + +## Job-Angabe beim Starten + +Der `record`-Befehl akzeptiert verschiedene Formate: + +```bash +./stream-recorder.sh record jobs/mein-stream.job # Vollständiger Pfad +./stream-recorder.sh record mein-stream.job # Nur Dateiname +./stream-recorder.sh record mein-stream # Nur Job-Name (ohne .job) +``` + +## Jobs anzeigen + +```bash +./stream-recorder.sh list +``` + +Zeigt alle Jobs mit Stream-Typ, URL, Status, Zeitplan, Segment-Einstellungen und Plik-Konfiguration. diff --git a/docs/konfiguration.md b/docs/konfiguration.md new file mode 100644 index 0000000..dd3c1ec --- /dev/null +++ b/docs/konfiguration.md @@ -0,0 +1,66 @@ +# Konfiguration + +Die Konfigurationsdatei liegt unter `config/stream-recorder.conf`. Sie wird aus der Vorlage `config/stream-recorder.conf.dist` erstellt: + +```bash +cp config/stream-recorder.conf.dist config/stream-recorder.conf +nano config/stream-recorder.conf +``` + +Die `.conf`-Datei wird von Git ignoriert und bleibt bei Updates erhalten. + +## Parameter + +### Basis-Einstellungen + +| Parameter | Standard | Beschreibung | +|-----------------|------------------------------|-------------------------------------------| +| `DOWNLOAD_PATH` | `/home/downloads/recordings` | Basis-Verzeichnis für Aufnahmen | +| `LOG_DIR` | `/var/log/stream-recorder` | Log-Verzeichnis | +| `LOG_LEVEL` | `INFO` | Log-Level: `DEBUG`, `INFO`, `WARN`, `ERROR` | +| `PID_DIR` | `/tmp/stream-recorder` | PID-Dateien für laufende Aufnahmen | +| `STATE_DIR` | `/var/lib/stream-recorder` | Status für bereits gestartete Zeitpläne | + +### Reconnect + +| Parameter | Standard | Beschreibung | +|---------------|----------|-------------------------------------------| +| `MAX_RETRIES` | `0` | Wiederholungsversuche (0 = unbegrenzt) | +| `RETRY_DELAY` | `5` | Wartezeit zwischen Versuchen (Sekunden) | + +Bei Verbindungsabbrüchen versucht das Script automatisch, die Verbindung wiederherzustellen. Bei jedem Reconnect wird eine neue Datei mit aktuellem Zeitstempel erzeugt. + +- `MAX_RETRIES=0` — Unbegrenzte Versuche (empfohlen für Langzeitaufnahmen) +- `MAX_RETRIES=5` — Maximal 5 Versuche, danach Abbruch mit Benachrichtigung + +### NTFY-Benachrichtigungen + +Push-Nachrichten via [ntfy](https://ntfy.sh) (selbst gehostet oder öffentlich). + +| Parameter | Standard | Beschreibung | +|---------------|----------|-------------------------------------------| +| `NTFY_URL` | (leer) | NTFY Server-URL inkl. Topic | +| `NTFY_TOKEN` | (leer) | NTFY Access-Token (optional) | +| `NTFY_EVENTS` | `error` | Benachrichtigungs-Events (kommasepariert) | + +Siehe [Benachrichtigungen](benachrichtigungen.md) für Details zu den Events. + +### Plik-Upload + +Fertige Aufnahmen auf einen [Plik](https://github.com/root-gg/plik)-Server hochladen. + +| Parameter | Standard | Beschreibung | +|----------------|----------|-------------------------------------------| +| `PLIK_URL` | (leer) | Plik Server-URL | +| `PLIK_API_KEY` | (leer) | Plik API-Key (optional) | +| `PLIK_TTL` | `30d` | Gültigkeitsdauer: `30d`, `24h`, Sekunden | + +Siehe [Plik-Upload](plik-upload.md) für die Einrichtung pro Job. + +## Alternative Konfigurationsdatei + +Eine andere Konfigurationsdatei kann per `-c` übergeben werden: + +```bash +./stream-recorder.sh -c /pfad/zur/config.conf record mein-stream +``` diff --git a/docs/plik-upload.md b/docs/plik-upload.md new file mode 100644 index 0000000..11db03e --- /dev/null +++ b/docs/plik-upload.md @@ -0,0 +1,62 @@ +# Plik-Upload + +Fertige Aufnahmen können automatisch auf einen [Plik](https://github.com/root-gg/plik)-Server hochgeladen werden. Die Browser-URL zum Download wird im Log und optional per NTFY-Benachrichtigung ausgegeben. + +## Einrichtung + +### 1. Plik-Server konfigurieren + +In `config/stream-recorder.conf`: + +```bash +PLIK_URL="https://plik.example.com" +PLIK_API_KEY="" # Optional, nur bei geschützten Servern +PLIK_TTL="30d" # Gültigkeitsdauer: 30d, 24h, oder Sekunden +``` + +### 2. Pro Job aktivieren + +In der Job-Datei: + +```bash +PLIK_ENABLED=true +``` + +### 3. Optional: Datei nach Upload löschen + +```bash +DELETE_AFTER_UPLOAD=true +``` + +## Verhalten + +- Upload nach jeder regulär abgeschlossenen Aufnahme +- Upload nach manuellem Stop (`stop`-Befehl) — die Teilaufnahme wird hochgeladen +- Upload nach Verbindungsabbruch, wenn eine Teildatei vorhanden ist +- Browser-Link und Datei-URL werden ins Log geschrieben +- Optional wird der Link per NTFY gesendet (Event: `upload`) + +## Mit Segment-Splitting + +Bei aktiviertem [Segment-Splitting](segment-splitting.md) wird jedes Segment einzeln hochgeladen: + +- Fertige Segmente werden während der Aufnahme hochgeladen (alle 60s geprüft) +- Das letzte Segment wird nach Ende der Aufnahme hochgeladen +- `DELETE_AFTER_UPLOAD=true` löscht jedes Segment nach dem Upload + +## Manueller Upload + +Eine vorhandene Datei kann auch manuell hochgeladen werden: + +```bash +./stream-recorder.sh upload /pfad/zur/aufnahme.mp4 "Stream-Name" +``` + +## Download-Link per Push + +Um den Plik-Link automatisch als Push-Nachricht zu erhalten: + +```bash +# In config/stream-recorder.conf +NTFY_EVENTS="start,stop,error,upload" +``` diff --git a/docs/segment-splitting.md b/docs/segment-splitting.md new file mode 100644 index 0000000..e8202b7 --- /dev/null +++ b/docs/segment-splitting.md @@ -0,0 +1,76 @@ +# Segment-Splitting + +Lange Aufnahmen (z.B. 8-Stunden-Streams) können automatisch in kleinere Dateien geteilt werden. Jedes Segment wird einzeln nach Plik hochgeladen, sobald es fertig ist. + +## Einrichtung + +In der Job-Datei `SEGMENT_DURATION` setzen: + +```bash +# Aufnahme alle 2 Stunden teilen +SEGMENT_DURATION="2h" + +# Plik-Upload aktivieren (empfohlen) +PLIK_ENABLED=true +``` + +## Dauerformate + +| Format | Bedeutung | +|----------|------------------| +| `30m` | Alle 30 Minuten | +| `1h` | Stündlich | +| `2h` | Alle 2 Stunden | +| `3600` | 3600 Sekunden | + +## So funktioniert's + +1. ffmpeg teilt die Aufnahme automatisch in Segmente der angegebenen Länge +2. Ein Hintergrund-Prozess prüft alle 60 Sekunden auf fertige Segmente +3. Fertige Segmente werden sofort nach Plik hochgeladen (falls aktiviert) +4. Beim Beenden (regulär oder per `stop`) wird das letzte Segment noch hochgeladen + +### Dateinamen + +Segmente erhalten fortlaufende Nummern: + +``` +Mein-Stream_20260907_200000_seg000.mp4 +Mein-Stream_20260907_200000_seg001.mp4 +Mein-Stream_20260907_200000_seg002.mp4 +``` + +## Kombination mit Plik + +Segment-Splitting ist besonders nützlich in Kombination mit Plik-Upload: + +```bash +SEGMENT_DURATION="1h" +PLIK_ENABLED=true +DELETE_AFTER_UPLOAD=true # Lokale Segmente nach Upload löschen +``` + +So wird jede Stunde ein Segment hochgeladen und lokal gelöscht. Das spart Speicherplatz bei langen Aufnahmen. + +## Kombination mit Zeitplanung + +Segment-Splitting und Zeitplanung lassen sich kombinieren: + +```bash +SCHEDULE_ENABLED=true +SCHEDULE_DAYS="*" +SCHEDULE_START="08:00" +SCHEDULE_STOP="20:00" +SEGMENT_DURATION="2h" +PLIK_ENABLED=true +``` + +Die 12-Stunden-Aufnahme wird in 6 Segmente zu je 2 Stunden geteilt, jedes wird nach Fertigstellung hochgeladen. + +## Verhalten bei Reconnect + +Wenn die Verbindung abbricht und wiederhergestellt wird, werden die bisherigen Segmente hochgeladen und eine neue Segment-Serie mit neuem Zeitstempel begonnen. + +## Status + +Mit `./stream-recorder.sh status` werden bei segmentierten Aufnahmen die Anzahl der Segmente und die Gesamtgröße angezeigt. diff --git a/docs/zeitplanung.md b/docs/zeitplanung.md new file mode 100644 index 0000000..048f904 --- /dev/null +++ b/docs/zeitplanung.md @@ -0,0 +1,80 @@ +# Zeitplanung + +Die Zeitplanung wird direkt in der Job-Datei konfiguriert — kein Cron-Wissen nötig. Ein systemd-Timer prüft jede Minute alle Jobs und startet/stoppt Aufnahmen automatisch. + +## Einrichtung + +1. **Job-Datei erstellen** und `SCHEDULE_*`-Parameter setzen +2. **Scheduler installieren** mit `sudo install/install.sh` (einmalig) +3. Fertig — der Timer prüft jede Minute alle Jobs + +## Zeitplan-Parameter + +| Parameter | Standard | Beschreibung | +|--------------------|----------|-------------------------------------------------| +| `SCHEDULE_ENABLED` | `false` | Zeitplan aktivieren (`true`/`false`) | +| `SCHEDULE_ONCE` | `false` | Einmaliger Termin statt wöchentlichem Zeitplan | +| `SCHEDULE_DATE` | (leer) | Datum für einmalige Termine (`YYYY-MM-DD`) | +| `SCHEDULE_DAYS` | `*` | Aufnahmetage für wöchentliche Zeitpläne | +| `SCHEDULE_START` | (leer) | Startzeit im Format `HH:MM` | +| `SCHEDULE_STOP` | (leer) | Endzeit im Format `HH:MM` (leer = kein Auto-Stop)| + +## Tagesformate + +| Format | Bedeutung | +|--------------|----------------------------| +| `*` | Täglich | +| `Mo-Fr` | Montag bis Freitag | +| `Sa,So` | Samstag und Sonntag | +| `Mo,Mi,Fr` | Einzelne Tage | +| `Mo-Fr,So` | Kombiniert | + +Die Tagesangaben funktionieren auf Deutsch (Mo, Di, Mi, Do, Fr, Sa, So) und Englisch (Mon, Tue, Wed, Thu, Fri, Sat, Sun). + +## Verhalten + +- **Start + Stop gesetzt:** Aufnahme läuft im angegebenen Zeitfenster +- **Nur Start gesetzt:** Aufnahme startet zur angegebenen Zeit, läuft bis `MAX_DURATION` oder manueller Stop +- **Einmalig:** Mit `SCHEDULE_ONCE=true` und `SCHEDULE_DATE="YYYY-MM-DD"` startet der Job nur an diesem Datum +- **Manuell gestartete Aufnahmen** werden vom Scheduler nicht beeinflusst +- **Nach Neustart** des Servers erkennt der Scheduler automatisch, welche Aufnahmen laufen sollten +- **Nachtübergreifend:** Start- und Stoppzeiten können über Mitternacht gehen (z.B. `23:00` bis `02:00`) + +## Beispiele + +### Tägliche Aufnahme von 20:00 bis 22:00 + +```bash +SCHEDULE_ENABLED=true +SCHEDULE_DAYS="*" +SCHEDULE_START="20:00" +SCHEDULE_STOP="22:00" +``` + +### Werktags um 18:00, 1 Stunde + +```bash +SCHEDULE_ENABLED=true +SCHEDULE_DAYS="Mo-Fr" +SCHEDULE_START="18:00" +MAX_DURATION="1h" +``` + +### Nur am Wochenende + +```bash +SCHEDULE_ENABLED=true +SCHEDULE_DAYS="Sa,So" +SCHEDULE_START="14:00" +SCHEDULE_STOP="18:00" +``` + +### Einmalige Aufnahme am 15.09.2026 + +```bash +SCHEDULE_ENABLED=true +SCHEDULE_ONCE=true +SCHEDULE_DATE="2026-09-15" +SCHEDULE_START="21:00" +MAX_DURATION="30m" +``` diff --git a/install/install.sh b/install/install.sh index 21a74c2..99f4838 100644 --- a/install/install.sh +++ b/install/install.sh @@ -39,6 +39,16 @@ fi # Hauptscript ausführbar machen chmod +x "${SCRIPT_DIR}/stream-recorder.sh" +# Konfiguration aus Vorlage erstellen, falls nicht vorhanden +if [[ ! -f "${SCRIPT_DIR}/config/stream-recorder.conf" ]]; then + if [[ -f "${SCRIPT_DIR}/config/stream-recorder.conf.dist" ]]; then + cp "${SCRIPT_DIR}/config/stream-recorder.conf.dist" "${SCRIPT_DIR}/config/stream-recorder.conf" + echo "Konfiguration erstellt: config/stream-recorder.conf" + echo " Bitte anpassen: nano ${SCRIPT_DIR}/config/stream-recorder.conf" + echo "" + fi +fi + echo "Installationspfad: ${SCRIPT_DIR}" echo "" @@ -85,9 +95,10 @@ echo "Der Scheduler prüft jetzt jede Minute die Job-Dateien" echo "und startet/stoppt Aufnahmen automatisch." echo "" echo "Nächste Schritte:" -echo " 1. Job-Datei erstellen: cp jobs/templates/example-rtmp.job.dist jobs/mein-stream.job" -echo " 2. Job-Datei bearbeiten: nano jobs/mein-stream.job" -echo " 3. SCHEDULE_ENABLED=true setzen und Zeiten konfigurieren" -echo " 4. Jobs prüfen: ./stream-recorder.sh list" +echo " 1. Konfiguration anpassen: nano ${SCRIPT_DIR}/config/stream-recorder.conf" +echo " 2. Job-Datei erstellen: cp jobs/templates/example-rtmp.job.dist jobs/mein-stream.job" +echo " 3. Job-Datei bearbeiten: nano jobs/mein-stream.job" +echo " 4. SCHEDULE_ENABLED=true setzen und Zeiten konfigurieren" +echo " 5. Jobs prüfen: ./stream-recorder.sh list" echo "" echo "Deinstallation: sudo install/uninstall.sh" diff --git a/jobs/templates/example-hls.job.dist b/jobs/templates/example-hls.job.dist index 9e32b48..4fe205f 100644 --- a/jobs/templates/example-hls.job.dist +++ b/jobs/templates/example-hls.job.dist @@ -22,6 +22,13 @@ MAX_DURATION="" # Zusätzliche ffmpeg-Argumente (optional) EXTRA_FFMPEG_ARGS="" +############################################### +# Segment-Splitting (optional) +# z.B. 1h = stündlich, 2h = alle 2 Stunden +############################################### + +SEGMENT_DURATION="" + ############################################### # Zeitplanung (optional) ############################################### diff --git a/jobs/templates/example-mp3.job.dist b/jobs/templates/example-mp3.job.dist index 7f070e4..2f9b11c 100644 --- a/jobs/templates/example-mp3.job.dist +++ b/jobs/templates/example-mp3.job.dist @@ -23,6 +23,13 @@ MAX_DURATION="1h" # Zusätzliche ffmpeg-Argumente (optional) EXTRA_FFMPEG_ARGS="" +############################################### +# Segment-Splitting (optional) +# z.B. 1h = stündlich, 2h = alle 2 Stunden +############################################### + +SEGMENT_DURATION="" + ############################################### # Zeitplanung (optional) ############################################### diff --git a/jobs/templates/example-rtmp.job.dist b/jobs/templates/example-rtmp.job.dist index 7d9e618..aad41c6 100644 --- a/jobs/templates/example-rtmp.job.dist +++ b/jobs/templates/example-rtmp.job.dist @@ -25,6 +25,16 @@ MAX_DURATION="" # Zusätzliche ffmpeg-Argumente (optional) EXTRA_FFMPEG_ARGS="" +############################################### +# Segment-Splitting (optional) +# Aufnahme in Teile zerlegen +# Jedes Segment wird einzeln nach Plik hochgeladen +# Beispiele: 1h = stündlich, 2h = alle 2 Stunden, 30m = alle 30 Minuten +# Leer = deaktiviert (eine durchgehende Datei) +############################################### + +SEGMENT_DURATION="" + ############################################### # Zeitplanung (optional) # Damit der Scheduler funktioniert, muss er diff --git a/stream-recorder.sh b/stream-recorder.sh index 1ab7df8..f227b4d 100644 --- a/stream-recorder.sh +++ b/stream-recorder.sh @@ -8,15 +8,15 @@ # Web: https://www.cleveradmin.de # Repository: https://git.techniverse.net/scriptos/stream-recorder.git # License: MIT -# Version: v2.6.2 -# Date: 06.09.2026 -# Modifications: Mehrtägige Aufnahmen nun möglich +# Version: v2.7.0 +# Date: 07.09.2026 +# Modifications: Segment-Splitting, erweiterter Status, verbessertes Stoppen ############################################### set -uo pipefail readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -readonly VERSION="2.6.0" +readonly VERSION="2.7.0" # --- Standardwerte (überschreibbar via Konfiguration) --- CONFIG_FILE="${SCRIPT_DIR}/config/stream-recorder.conf" @@ -224,6 +224,57 @@ parse_duration() { echo "$total" } +format_elapsed() { + local seconds=$1 + local d=$((seconds / 86400)) + local h=$(( (seconds % 86400) / 3600 )) + local m=$(( (seconds % 3600) / 60 )) + local s=$((seconds % 60)) + + if [[ $d -gt 0 ]]; then + printf "%dd %dh %dm" "$d" "$h" "$m" + elif [[ $h -gt 0 ]]; then + printf "%dh %dm %ds" "$h" "$m" "$s" + else + printf "%dm %ds" "$m" "$s" + fi +} + +format_size() { + local bytes=$1 + if [[ $bytes -ge 1073741824 ]]; then + awk "BEGIN {printf \"%.1f GB\", $bytes/1073741824}" + elif [[ $bytes -ge 1048576 ]]; then + awk "BEGIN {printf \"%.1f MB\", $bytes/1048576}" + elif [[ $bytes -ge 1024 ]]; then + awk "BEGIN {printf \"%.1f KB\", $bytes/1024}" + else + printf "%d B" "$bytes" + fi +} + +write_job_info() { + local job_id="$1" stream_name="$2" output_dir="$3" output_file="$4" + local stream_type="$5" segment_mode="$6" job_file="$7" + local info_file="${PID_DIR}/${job_id}.info" + + cat > "$info_file" </dev/null || true +} + # --- Tages-/Zeitplanung --- _day_name_to_num() { @@ -298,7 +349,13 @@ load_config() { # shellcheck source=/dev/null source "$CONFIG_FILE" else - log "WARN" "Konfigurationsdatei nicht gefunden: ${CONFIG_FILE} - verwende Standardwerte" + local dist_file="${CONFIG_FILE}.dist" + if [[ -f "$dist_file" ]]; then + log "WARN" "Konfigurationsdatei nicht gefunden: ${CONFIG_FILE}" + log "INFO" "Vorlage kopieren: cp ${dist_file} ${CONFIG_FILE}" + else + log "WARN" "Konfigurationsdatei nicht gefunden: ${CONFIG_FILE} - verwende Standardwerte" + fi fi mkdir -p "$DOWNLOAD_PATH" "$LOG_DIR" "$PID_DIR" "$STATE_DIR" 2>/dev/null || true } @@ -360,6 +417,7 @@ load_job() { SCHEDULE_STOP="" PLIK_ENABLED="false" DELETE_AFTER_UPLOAD="false" + SEGMENT_DURATION="" if [[ ! -f "$job_file" ]]; then log "ERROR" "Job-Datei nicht gefunden: ${job_file}" @@ -462,6 +520,58 @@ mark_schedule_started() { fi } +# ============================================= +# Segment-Upload +# ============================================= + +_segment_upload_loop() { + local output_dir="$1" segment_glob="$2" stream_name="$3" + local uploaded_list="$4" stop_flag="$5" + + while [[ ! -f "$stop_flag" ]]; do + sleep 60 + [[ -f "$stop_flag" ]] && break + + local -a all_segments=() + for f in "$output_dir"/$segment_glob; do + [[ -f "$f" ]] && all_segments+=("$f") + done + + IFS=$'\n' all_segments=($(sort <<<"${all_segments[*]}")); unset IFS + + [[ ${#all_segments[@]} -lt 2 ]] && continue + + local i + for (( i=0; i < ${#all_segments[@]} - 1; i++ )); do + local seg="${all_segments[$i]}" + if ! grep -qxF "$seg" "$uploaded_list" 2>/dev/null; then + if [[ -s "$seg" ]]; then + log "INFO" "Segment-Upload: $(basename "$seg")" + upload_to_plik "$seg" "$stream_name" && echo "$seg" >> "$uploaded_list" + fi + fi + done + done +} + +_upload_remaining_segments() { + local output_dir="$1" segment_glob="$2" stream_name="$3" uploaded_list="$4" + + local count=0 + for seg in "$output_dir"/$segment_glob; do + [[ -f "$seg" ]] || continue + [[ -s "$seg" ]] || continue + if ! grep -qxF "$seg" "$uploaded_list" 2>/dev/null; then + log "INFO" "Segment-Upload (verbleibend): $(basename "$seg")" + upload_to_plik "$seg" "$stream_name" && echo "$seg" >> "$uploaded_list" + count=$((count + 1)) + fi + done + + [[ $count -gt 0 ]] && log "INFO" "${count} verbleibende(s) Segment(e) hochgeladen" + rm -f "$uploaded_list" 2>/dev/null || true +} + # ============================================= # Aufnahme # ============================================= @@ -480,12 +590,26 @@ record_stream() { local ext="${OUTPUT_FORMAT}" [[ -z "$ext" ]] && ext=$(default_extension "$stream_type") + local segment_seconds="" + local segment_mode=false + if [[ -n "${SEGMENT_DURATION:-}" ]]; then + segment_seconds=$(parse_duration "$SEGMENT_DURATION") + if [[ -n "$segment_seconds" && "$segment_seconds" -gt 0 ]]; then + segment_mode=true + fi + fi + local safe_name safe_name=$(echo "$STREAM_NAME" | tr -cs 'A-Za-z0-9_-' '_' | sed 's/_$//') local timestamp timestamp=$(date +'%Y%m%d_%H%M%S') local output_dir="${DOWNLOAD_PATH}/${safe_name}" local output_file="${output_dir}/${safe_name}_${timestamp}.${ext}" + local output_pattern="" + if $segment_mode; then + output_file="${output_dir}/${safe_name}_${timestamp}_seg%03d.${ext}" + output_pattern="${safe_name}_${timestamp}_seg*.${ext}" + fi local pid_file="${PID_DIR}/${job_id}.pid" local ffmpeg_log="${LOG_DIR}/ffmpeg_${job_id}.log" @@ -496,8 +620,13 @@ record_stream() { return 1 fi - log "INFO" "Starte Aufnahme: ${STREAM_NAME} [${stream_type}] -> ${output_file}" + if $segment_mode; then + log "INFO" "Starte Aufnahme: ${STREAM_NAME} [${stream_type}] -> ${output_dir} (Segment: alle $(format_elapsed "$segment_seconds"))" + else + log "INFO" "Starte Aufnahme: ${STREAM_NAME} [${stream_type}] -> ${output_file}" + fi notify "start" "Aufnahme gestartet" "${STREAM_NAME} [${stream_type}]" + write_job_info "$job_id" "$STREAM_NAME" "$output_dir" "$output_file" "$stream_type" "$segment_mode" "$job_file" local ffmpeg_loglevel="warning" [[ "$VERBOSE" == "true" ]] && ffmpeg_loglevel="info" @@ -509,6 +638,10 @@ record_stream() { *) ffmpeg_args+=(-i "$STREAM_URL" -c copy) ;; esac + if $segment_mode; then + ffmpeg_args+=(-f segment -segment_time "$segment_seconds" -reset_timestamps 1) + fi + [[ -n "$MAX_DURATION" ]] && ffmpeg_args+=(-t "$MAX_DURATION") if [[ -n "$EXTRA_FFMPEG_ARGS" ]]; then @@ -521,16 +654,33 @@ record_stream() { _SR_FFMPEG_PID="" _SR_PID_FILE="$pid_file" + _SR_JOB_ID="$job_id" + _SR_SEGMENT_STOP_FLAG="" + _SR_SEGMENT_UPLOADER_PID="" _cleanup() { + if [[ -n "${_SR_SEGMENT_STOP_FLAG:-}" ]]; then + touch "$_SR_SEGMENT_STOP_FLAG" 2>/dev/null || true + fi + if [[ -n "${_SR_SEGMENT_UPLOADER_PID:-}" ]] && kill -0 "$_SR_SEGMENT_UPLOADER_PID" 2>/dev/null; then + wait "$_SR_SEGMENT_UPLOADER_PID" 2>/dev/null || true + fi if [[ -n "${_SR_FFMPEG_PID:-}" ]] && kill -0 "$_SR_FFMPEG_PID" 2>/dev/null; then kill -INT "$_SR_FFMPEG_PID" 2>/dev/null || true wait "$_SR_FFMPEG_PID" 2>/dev/null || true fi rm -f "${_SR_PID_FILE:-}" rm -f "${_SR_PID_FILE%.pid}.scheduled" 2>/dev/null || true + remove_job_info "${_SR_JOB_ID:-}" + rm -f "${_SR_SEGMENT_STOP_FLAG:-}" 2>/dev/null || true } trap _cleanup EXIT TERM INT + local _sr_uploaded_list="" + if $segment_mode; then + _sr_uploaded_list=$(mktemp) + _SR_SEGMENT_STOP_FLAG=$(mktemp -u) + fi + local retry=0 while true; do if [[ "$VERBOSE" == "true" ]]; then @@ -541,12 +691,28 @@ record_stream() { _SR_FFMPEG_PID=$! echo "$_SR_FFMPEG_PID" > "$pid_file" + if $segment_mode; then + rm -f "$_SR_SEGMENT_STOP_FLAG" 2>/dev/null || true + _segment_upload_loop "$output_dir" "$output_pattern" "$STREAM_NAME" "$_sr_uploaded_list" "$_SR_SEGMENT_STOP_FLAG" & + _SR_SEGMENT_UPLOADER_PID=$! + fi + local ret=0 wait "$_SR_FFMPEG_PID" || ret=$? _SR_FFMPEG_PID="" + if [[ -n "${_SR_SEGMENT_UPLOADER_PID:-}" ]] && kill -0 "$_SR_SEGMENT_UPLOADER_PID" 2>/dev/null; then + touch "$_SR_SEGMENT_STOP_FLAG" 2>/dev/null || true + wait "$_SR_SEGMENT_UPLOADER_PID" 2>/dev/null || true + _SR_SEGMENT_UPLOADER_PID="" + fi + if [[ $ret -eq 0 ]]; then - if [[ -s "$output_file" ]]; then + if $segment_mode; then + _upload_remaining_segments "$output_dir" "$output_pattern" "$STREAM_NAME" "$_sr_uploaded_list" + log "INFO" "Aufnahme abgeschlossen: ${STREAM_NAME} (segmentiert)" + notify "stop" "Aufnahme abgeschlossen" "${STREAM_NAME} (segmentiert)" + elif [[ -s "$output_file" ]]; then log "INFO" "Aufnahme abgeschlossen: ${output_file}" notify "stop" "Aufnahme abgeschlossen" "${STREAM_NAME}: ${output_file}" upload_to_plik "$output_file" "$STREAM_NAME" @@ -561,7 +727,9 @@ record_stream() { if [[ $ret -eq 143 || $ret -eq 130 ]]; then log "INFO" "Aufnahme gestoppt: ${STREAM_NAME}" notify "stop" "Aufnahme gestoppt" "${STREAM_NAME} wurde gestoppt" - if [[ -s "$output_file" ]]; then + if $segment_mode; then + _upload_remaining_segments "$output_dir" "$output_pattern" "$STREAM_NAME" "$_sr_uploaded_list" + elif [[ -s "$output_file" ]]; then upload_to_plik "$output_file" "$STREAM_NAME" fi break @@ -570,7 +738,9 @@ record_stream() { if [[ ! -f "$pid_file" ]]; then log "INFO" "Aufnahme gestoppt: ${STREAM_NAME}" notify "stop" "Aufnahme gestoppt" "${STREAM_NAME} wurde gestoppt" - if [[ -s "$output_file" ]]; then + if $segment_mode; then + _upload_remaining_segments "$output_dir" "$output_pattern" "$STREAM_NAME" "$_sr_uploaded_list" + elif [[ -s "$output_file" ]]; then upload_to_plik "$output_file" "$STREAM_NAME" fi break @@ -592,18 +762,38 @@ record_stream() { log "WARN" "Verbindung zu '${STREAM_NAME}' verloren (Code: ${ret})${retry_info}. Reconnect in ${RETRY_DELAY}s..." notify "error" "Stream abgerissen" "${STREAM_NAME}: Verbindung verloren (Code: ${ret}). Reconnect in ${RETRY_DELAY}s..." "default" + + if $segment_mode; then + _upload_remaining_segments "$output_dir" "$output_pattern" "$STREAM_NAME" "$_sr_uploaded_list" + fi + sleep "$RETRY_DELAY" timestamp=$(date +'%Y%m%d_%H%M%S') - output_file="${output_dir}/${safe_name}_${timestamp}.${ext}" + if $segment_mode; then + output_file="${output_dir}/${safe_name}_${timestamp}_seg%03d.${ext}" + output_pattern="${safe_name}_${timestamp}_seg*.${ext}" + else + output_file="${output_dir}/${safe_name}_${timestamp}.${ext}" + fi ffmpeg_args[-1]="$output_file" done trap - EXIT TERM INT rm -f "$pid_file" rm -f "${pid_file%.pid}.scheduled" 2>/dev/null || true + remove_job_info "$job_id" + rm -f "${_SR_SEGMENT_STOP_FLAG:-}" 2>/dev/null || true - if [[ -f "$output_file" && ! -s "$output_file" ]]; then + if $segment_mode; then + for seg in "$output_dir"/$output_pattern; do + [[ -f "$seg" ]] || continue + if [[ ! -s "$seg" ]]; then + rm -f "$seg" + log "WARN" "Leeres Segment entfernt: ${seg}" + fi + done + elif [[ -f "$output_file" && ! -s "$output_file" ]]; then rm -f "$output_file" log "WARN" "Leere Ausgabedatei entfernt: ${output_file}" fi @@ -887,6 +1077,19 @@ create_job() { fi fi + echo "" + echo "--- Segment-Splitting (optional) ---" + local segment_duration="" + read -rp "Aufnahme in Segmente teilen? (z.B. 1h, 2h oder leer=nein): " segment_input + if [[ -n "$segment_input" ]]; then + segment_duration=$(parse_duration "$segment_input") + if [[ -n "$segment_duration" && "$segment_duration" -gt 0 ]]; then + echo " Segmente alle $(format_elapsed "$segment_duration")" + else + segment_duration="" + fi + fi + echo "" echo "--- Plik-Upload (optional) ---" local plik_enabled="false" @@ -930,6 +1133,11 @@ create_job() { duration_display+="${d_h}h ${d_m}m" fi echo " Dauer: ${duration_display}" + if [[ -n "$segment_duration" ]]; then + echo " Segmente: alle $(format_elapsed "$segment_duration")" + else + echo " Segmente: deaktiviert" + fi if [[ "$sched_enabled" == "true" ]]; then if [[ "$sched_once" == "true" ]]; then echo " Zeitplan: einmalig ${sched_date} ${sched_start}${sched_stop:+ - ${sched_stop}}" @@ -963,6 +1171,13 @@ OUTPUT_FORMAT="${ext}" MAX_DURATION="${duration}" EXTRA_FFMPEG_ARGS="" +############################################### +# Segment-Splitting (optional) +# Aufnahme in Teile zerlegen, z.B. 1h, 2h, 30m +############################################### + +SEGMENT_DURATION="${segment_duration}" + ############################################### # Zeitplanung (optional) ############################################### @@ -1032,6 +1247,13 @@ list_jobs() { extra_info+=" (Dauer: ${MAX_DURATION}s)" fi fi + if [[ -n "${SEGMENT_DURATION:-}" ]]; then + local seg_secs + seg_secs=$(parse_duration "$SEGMENT_DURATION") + if [[ -n "$seg_secs" && "$seg_secs" -gt 0 ]]; then + extra_info+="${extra_info:+ | }Segmente: alle $(format_elapsed "$seg_secs")" + fi + fi if [[ "${PLIK_ENABLED:-false}" == "true" ]]; then extra_info+="${extra_info:+ | }Plik-Upload: an" fi @@ -1062,7 +1284,7 @@ list_jobs() { show_status() { echo "Laufende Aufnahmen:" - echo "$(printf '=%.0s' {1..50})" + echo "$(printf '=%.0s' {1..60})" echo "" local found=false @@ -1078,9 +1300,48 @@ show_status() { found=true local mode="manuell" [[ -f "${PID_DIR}/${job_id}.scheduled" ]] && mode="geplant" - printf " %-20s PID: %-10s [%s]\n" "$job_id" "$pid" "$mode" + + local info_file="${PID_DIR}/${job_id}.info" + if [[ -f "$info_file" ]]; then + local STREAM_NAME="" STREAM_TYPE="" OUTPUT_DIR="" OUTPUT_FILE="" + local START_TIME="" START_EPOCH="" SEGMENT_MODE="" + # shellcheck source=/dev/null + source "$info_file" + + printf " %-20s %s\n" "$job_id" "$STREAM_NAME" + printf " %-20s PID: %s | Modus: %s | Typ: %s\n" "" "$pid" "$mode" "$STREAM_TYPE" + printf " %-20s Gestartet: %s" "" "$START_TIME" + + if [[ -n "$START_EPOCH" ]]; then + local now_epoch elapsed + now_epoch=$(date +%s) + elapsed=$((now_epoch - START_EPOCH)) + printf " | Laufzeit: %s" "$(format_elapsed "$elapsed")" + fi + echo "" + + local total_size=0 + if [[ "$SEGMENT_MODE" == "true" && -n "$OUTPUT_DIR" ]]; then + local seg_count=0 + for seg in "$OUTPUT_DIR"/*_seg*; do + [[ -f "$seg" ]] || continue + local s + s=$(stat -c%s "$seg" 2>/dev/null || echo 0) + total_size=$((total_size + s)) + seg_count=$((seg_count + 1)) + done + printf " %-20s Verzeichnis: %s | Segmente: %d | Größe: %s\n" "" "$OUTPUT_DIR" "$seg_count" "$(format_size "$total_size")" + elif [[ -n "$OUTPUT_FILE" && -f "$OUTPUT_FILE" ]]; then + total_size=$(stat -c%s "$OUTPUT_FILE" 2>/dev/null || echo 0) + printf " %-20s Datei: %s | Größe: %s\n" "" "$OUTPUT_FILE" "$(format_size "$total_size")" + fi + else + printf " %-20s PID: %-10s [%s]\n" "$job_id" "$pid" "$mode" + fi + echo "" else rm -f "$pid_file" + rm -f "${PID_DIR}/${job_id}.info" 2>/dev/null || true fi done @@ -1181,10 +1442,10 @@ Befehle: create Neuen Job interaktiv erstellen quick Interaktive Schnellaufnahme (URL eingeben) upload Vorhandene Datei nach Plik hochladen - stop Aufnahme stoppen + stop Aufnahme stoppen (Teilaufnahme wird hochgeladen) stop-all Alle Aufnahmen stoppen list Jobs und deren Zeitpläne anzeigen - status Laufende Aufnahmen und Scheduler-Status + status Laufende Aufnahmen mit Details anzeigen scheduler Zeitplanung prüfen und Jobs starten/stoppen logs Log-Ausgabe live verfolgen @@ -1195,6 +1456,11 @@ Optionen: -h, --help Diese Hilfe -v, --version Version +Segment-Splitting: + Aufnahmen können in Segmente (z.B. 1h, 2h) geteilt werden. + Jedes Segment wird einzeln nach Plik hochgeladen. + Konfiguration: SEGMENT_DURATION in der Job-Datei. + Zeitplanung: Aufnahmen werden über die Job-Dateien geplant (SCHEDULE_*). Der Scheduler wird automatisch per systemd-Timer ausgeführt. @@ -1204,6 +1470,8 @@ Beispiele: $(basename "$0") record mein-stream $(basename "$0") quick $(basename "$0") -V record mein-stream + $(basename "$0") status + $(basename "$0") stop mein-stream $(basename "$0") list $(basename "$0") logs