Release: v2.4.0
This commit is contained in:
@@ -81,6 +81,7 @@ Die Konfigurationsdatei liegt unter `config/stream-recorder.conf`:
|
||||
| `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 |
|
||||
@@ -122,7 +123,7 @@ nano jobs/mein-stream.job
|
||||
| `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 | Maximale Dauer in Sekunden (leer = unbegrenzt) |
|
||||
| `MAX_DURATION` | Nein | Maximale Dauer, z.B. `300`, `5m`, `1h30m` (leer = unbegrenzt) |
|
||||
| `EXTRA_FFMPEG_ARGS`| Nein | Zusätzliche ffmpeg-Parameter |
|
||||
| `PLIK_ENABLED` | Nein | Plik-Upload aktivieren (`true`/`false`) |
|
||||
|
||||
@@ -131,7 +132,9 @@ nano jobs/mein-stream.job
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|--------------------|----------|-------------------------------------------------|
|
||||
| `SCHEDULE_ENABLED` | `false` | Zeitplan aktivieren (`true`/`false`) |
|
||||
| `SCHEDULE_DAYS` | `*` | Aufnahmetage (siehe Formate unten) |
|
||||
| `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)|
|
||||
|
||||
@@ -147,6 +150,8 @@ nano jobs/mein-stream.job
|
||||
|
||||
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
|
||||
@@ -163,12 +168,26 @@ SCHEDULE_STOP="22:00"
|
||||
```bash
|
||||
STREAM_NAME="Mein-Radiosender"
|
||||
STREAM_URL="https://example.com/stream.mp3"
|
||||
MAX_DURATION="3600"
|
||||
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
|
||||
@@ -177,6 +196,9 @@ SCHEDULE_START="18:00"
|
||||
# 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
|
||||
|
||||
@@ -186,6 +208,9 @@ SCHEDULE_START="18:00"
|
||||
# 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
|
||||
|
||||
@@ -239,6 +264,7 @@ Die Zeitplanung wird direkt in der Job-Datei konfiguriert - kein Cron nötig.
|
||||
|
||||
- **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
|
||||
|
||||
@@ -293,8 +319,10 @@ PLIK_ENABLED=true
|
||||
|
||||
- Der Upload erfolgt automatisch nach jeder regulär abgeschlossenen Aufnahme
|
||||
- Bei manuellem Stop oder Verbindungsabbruch wird kein Upload ausgeführt
|
||||
- Der Download-Link wird im Log ausgegeben
|
||||
- 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
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ LOG_LEVEL="INFO"
|
||||
# PID-Verzeichnis für laufende Aufnahmen
|
||||
PID_DIR="/tmp/stream-recorder"
|
||||
|
||||
# Persistentes Status-Verzeichnis für geplante Läufe
|
||||
STATE_DIR="/var/lib/stream-recorder"
|
||||
|
||||
# Anzahl Wiederholungsversuche bei Verbindungsabbruch
|
||||
# 0 = unbegrenzt (empfohlen für Langzeitaufnahmen)
|
||||
MAX_RETRIES=5
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
###############################################
|
||||
# stream-recorder - Job: Euer-Radio.DE-Main 30min
|
||||
###############################################
|
||||
|
||||
STREAM_NAME="Euer-Radio.DE-Main-30min"
|
||||
STREAM_URL="rtmp://rtmp23.euer.tv/live/1080p"
|
||||
STREAM_TYPE="rtmp"
|
||||
OUTPUT_FORMAT=""
|
||||
MAX_DURATION="30m"
|
||||
EXTRA_FFMPEG_ARGS=""
|
||||
|
||||
###############################################
|
||||
# Zeitplanung (optional)
|
||||
###############################################
|
||||
|
||||
SCHEDULE_ENABLED=false
|
||||
SCHEDULE_ONCE=false
|
||||
SCHEDULE_DATE=""
|
||||
SCHEDULE_DAYS="*"
|
||||
SCHEDULE_START=""
|
||||
SCHEDULE_STOP=""
|
||||
|
||||
###############################################
|
||||
# Plik-Upload (optional)
|
||||
###############################################
|
||||
|
||||
PLIK_ENABLED=true
|
||||
@@ -16,7 +16,7 @@ STREAM_TYPE="hls"
|
||||
# Ausgabeformat (mp4 oder mkv empfohlen)
|
||||
OUTPUT_FORMAT="mp4"
|
||||
|
||||
# Maximale Aufnahmedauer in Sekunden
|
||||
# Maximale Aufnahmedauer (leer = unbegrenzt, z.B. 5m oder 1h)
|
||||
MAX_DURATION=""
|
||||
|
||||
# Zusätzliche ffmpeg-Argumente (optional)
|
||||
@@ -27,6 +27,8 @@ EXTRA_FFMPEG_ARGS=""
|
||||
###############################################
|
||||
|
||||
SCHEDULE_ENABLED=false
|
||||
SCHEDULE_ONCE=false
|
||||
SCHEDULE_DATE=""
|
||||
SCHEDULE_DAYS="*"
|
||||
SCHEDULE_START="20:00"
|
||||
SCHEDULE_STOP="22:00"
|
||||
|
||||
@@ -16,9 +16,9 @@ STREAM_TYPE="auto"
|
||||
# Ausgabeformat
|
||||
OUTPUT_FORMAT=""
|
||||
|
||||
# Maximale Aufnahmedauer in Sekunden (leer = unbegrenzt)
|
||||
# Für Radio-Mitschnitte empfohlen, z.B. 3600 für 1 Stunde
|
||||
MAX_DURATION="3600"
|
||||
# Maximale Aufnahmedauer (leer = unbegrenzt)
|
||||
# Für Radio-Mitschnitte empfohlen, z.B. 1h für 1 Stunde
|
||||
MAX_DURATION="1h"
|
||||
|
||||
# Zusätzliche ffmpeg-Argumente (optional)
|
||||
EXTRA_FFMPEG_ARGS=""
|
||||
@@ -28,6 +28,8 @@ EXTRA_FFMPEG_ARGS=""
|
||||
###############################################
|
||||
|
||||
SCHEDULE_ENABLED=false
|
||||
SCHEDULE_ONCE=false
|
||||
SCHEDULE_DATE=""
|
||||
SCHEDULE_DAYS="Mo-Fr"
|
||||
SCHEDULE_START="18:00"
|
||||
SCHEDULE_STOP=""
|
||||
|
||||
@@ -18,8 +18,8 @@ STREAM_TYPE="auto"
|
||||
# Leer lassen für automatische Erkennung basierend auf Stream-Typ
|
||||
OUTPUT_FORMAT=""
|
||||
|
||||
# Maximale Aufnahmedauer in Sekunden (leer = unbegrenzt)
|
||||
# Beispiel: 3600 = 1 Stunde, 7200 = 2 Stunden
|
||||
# Maximale Aufnahmedauer (leer = unbegrenzt)
|
||||
# Beispiele: 300 = 5 Minuten, 5m = 5 Minuten, 1h30m = 90 Minuten
|
||||
MAX_DURATION=""
|
||||
|
||||
# Zusätzliche ffmpeg-Argumente (optional)
|
||||
@@ -34,7 +34,14 @@ EXTRA_FFMPEG_ARGS=""
|
||||
# Zeitplan aktivieren (true/false)
|
||||
SCHEDULE_ENABLED=false
|
||||
|
||||
# Aufnahmetage
|
||||
# Einmalige Aufnahme statt wöchentlichem Zeitplan (true/false)
|
||||
SCHEDULE_ONCE=false
|
||||
|
||||
# Datum für einmalige Aufnahme (YYYY-MM-DD)
|
||||
# Nur relevant, wenn SCHEDULE_ONCE=true ist
|
||||
SCHEDULE_DATE=""
|
||||
|
||||
# Aufnahmetage für wöchentliche Zeitpläne
|
||||
# * = täglich
|
||||
# Mo-Fr = Montag bis Freitag
|
||||
# Sa,So = Samstag und Sonntag
|
||||
|
||||
+215
-49
@@ -8,15 +8,15 @@
|
||||
# Web: https://www.cleveradmin.de
|
||||
# Repository: https://git.techniverse.net/scriptos/stream-recorder.git
|
||||
# License: MIT
|
||||
# Version: v2.3.0
|
||||
# Version: v2.4.0
|
||||
# Date: 06.09.2026
|
||||
# Modifications: Grundlegende Überarbeitung + viele neue Funktionen implementiert
|
||||
# Modifications: Scheduler-Watchdog stabilisiert, Plik-Upload repariert, einmalige Zeitplanung ergänzt
|
||||
###############################################
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly VERSION="2.3.0"
|
||||
readonly VERSION="2.4.0"
|
||||
|
||||
# --- Standardwerte (überschreibbar via Konfiguration) ---
|
||||
CONFIG_FILE="${SCRIPT_DIR}/config/stream-recorder.conf"
|
||||
@@ -25,6 +25,7 @@ DOWNLOAD_PATH="/home/downloads/recordings"
|
||||
LOG_DIR="/var/log/stream-recorder"
|
||||
LOG_LEVEL="INFO"
|
||||
PID_DIR="/tmp/stream-recorder"
|
||||
STATE_DIR="/var/lib/stream-recorder"
|
||||
MAX_RETRIES=0
|
||||
RETRY_DELAY=5
|
||||
|
||||
@@ -40,6 +41,7 @@ PLIK_TTL="30d"
|
||||
|
||||
# --- Laufzeit-Flags ---
|
||||
VERBOSE=false
|
||||
FOREGROUND=false
|
||||
|
||||
# =============================================
|
||||
# Hilfsfunktionen
|
||||
@@ -103,13 +105,36 @@ notify() {
|
||||
disown 2>/dev/null || true
|
||||
}
|
||||
|
||||
plik_browser_url_from_download_url() {
|
||||
local download_url="$1"
|
||||
local plik_base="${PLIK_URL%/}"
|
||||
local file_path="${download_url#"$plik_base"/file/}"
|
||||
|
||||
if [[ "$file_path" != "$download_url" ]]; then
|
||||
local upload_id="${file_path%%/*}"
|
||||
if [[ -n "$upload_id" && "$upload_id" != "$file_path" ]]; then
|
||||
echo "${plik_base}/#/?id=${upload_id}"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$download_url"
|
||||
}
|
||||
|
||||
upload_to_plik() {
|
||||
local file="$1"
|
||||
local stream_name="$2"
|
||||
|
||||
[[ "${PLIK_ENABLED:-false}" != "true" ]] && return
|
||||
[[ -z "${PLIK_URL:-}" ]] && return
|
||||
[[ ! -f "$file" ]] && return
|
||||
if [[ ! -f "$file" ]]; then
|
||||
log "WARN" "Plik-Upload übersprungen: Datei nicht gefunden: ${file}"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -s "$file" ]]; then
|
||||
log "WARN" "Plik-Upload übersprungen: Datei ist leer: ${file}"
|
||||
return 1
|
||||
fi
|
||||
command -v curl &>/dev/null || { log "WARN" "curl nicht installiert - Plik-Upload übersprungen"; return; }
|
||||
|
||||
local filename
|
||||
@@ -117,8 +142,7 @@ upload_to_plik() {
|
||||
|
||||
local plik_base="${PLIK_URL%/}"
|
||||
|
||||
local -a curl_args=(-s --max-time 300)
|
||||
curl_args+=(-X POST)
|
||||
local -a curl_args=(-sS --max-time 7200)
|
||||
|
||||
if [[ -n "${PLIK_API_KEY:-}" ]]; then
|
||||
curl_args+=(-H "X-PlikToken: ${PLIK_API_KEY}")
|
||||
@@ -136,51 +160,44 @@ upload_to_plik() {
|
||||
fi
|
||||
fi
|
||||
|
||||
curl_args+=(-F "file=@${file};filename=${filename}")
|
||||
[[ -n "$ttl_seconds" ]] && curl_args+=(-F "ttl=${ttl_seconds}")
|
||||
|
||||
log "INFO" "Plik-Upload: ${filename} -> ${plik_base}"
|
||||
|
||||
local create_json="{\"files\":[{\"fileName\":\"${filename}\"}]"
|
||||
if [[ -n "$ttl_seconds" ]]; then
|
||||
create_json+=",\"ttl\":${ttl_seconds}"
|
||||
fi
|
||||
create_json+="}"
|
||||
local upload_tmp upload_response http_code curl_status=0
|
||||
upload_tmp=$(mktemp)
|
||||
http_code=$(curl "${curl_args[@]}" \
|
||||
-w "%{http_code}" \
|
||||
-o "$upload_tmp" \
|
||||
"${plik_base}" 2>/dev/null) || curl_status=$?
|
||||
upload_response=$(cat "$upload_tmp" 2>/dev/null || true)
|
||||
rm -f "$upload_tmp"
|
||||
|
||||
local create_response
|
||||
create_response=$(curl "${curl_args[@]}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$create_json" \
|
||||
"${plik_base}/upload" 2>/dev/null)
|
||||
|
||||
if [[ -z "$create_response" ]]; then
|
||||
log "ERROR" "Plik-Upload fehlgeschlagen: Keine Antwort vom Server"
|
||||
if [[ $curl_status -ne 0 || ! "$http_code" =~ ^2[0-9][0-9]$ ]]; then
|
||||
log "ERROR" "Plik-Upload fehlgeschlagen: Datei konnte nicht hochgeladen werden (HTTP ${http_code:-000}, curl ${curl_status})"
|
||||
[[ -n "$upload_response" ]] && log "DEBUG" "Plik-Antwort: ${upload_response}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local upload_id file_id
|
||||
upload_id=$(echo "$create_response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
file_id=$(echo "$create_response" | grep -o '"id":"[^"]*"' | tail -1 | cut -d'"' -f4)
|
||||
|
||||
if [[ -z "$upload_id" || -z "$file_id" ]]; then
|
||||
log "ERROR" "Plik-Upload fehlgeschlagen: Upload konnte nicht erstellt werden"
|
||||
log "DEBUG" "Plik-Antwort: ${create_response}"
|
||||
local download_url
|
||||
download_url=$(printf '%s\n' "$upload_response" | grep -Eo 'https?://[^[:space:]]+' | tail -1)
|
||||
if [[ -z "$download_url" ]]; then
|
||||
log "ERROR" "Plik-Upload fehlgeschlagen: Keine Download-URL in der Antwort"
|
||||
log "DEBUG" "Plik-Antwort: ${upload_response}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local -a upload_curl_args=(-s --max-time 600)
|
||||
if [[ -n "${PLIK_API_KEY:-}" ]]; then
|
||||
upload_curl_args+=(-H "X-PlikToken: ${PLIK_API_KEY}")
|
||||
local browser_url
|
||||
browser_url=$(plik_browser_url_from_download_url "$download_url")
|
||||
|
||||
log "INFO" "Plik-Upload abgeschlossen: ${browser_url}"
|
||||
if [[ "$browser_url" != "$download_url" ]]; then
|
||||
log "INFO" "Plik-Datei-URL: ${download_url}"
|
||||
fi
|
||||
notify "upload" "Plik-Upload" "${stream_name}: ${browser_url}"
|
||||
|
||||
local upload_response
|
||||
upload_response=$(curl "${upload_curl_args[@]}" \
|
||||
-X POST \
|
||||
-T "$file" \
|
||||
"${plik_base}/file/${upload_id}/${file_id}/${filename}" 2>/dev/null)
|
||||
|
||||
local download_url="${plik_base}/file/${upload_id}/${file_id}/${filename}"
|
||||
log "INFO" "Plik-Upload abgeschlossen: ${download_url}"
|
||||
notify "upload" "Plik-Upload" "${stream_name}: ${download_url}"
|
||||
|
||||
echo "$download_url"
|
||||
echo "$browser_url"
|
||||
}
|
||||
|
||||
parse_duration() {
|
||||
@@ -277,7 +294,7 @@ load_config() {
|
||||
else
|
||||
log "WARN" "Konfigurationsdatei nicht gefunden: ${CONFIG_FILE} - verwende Standardwerte"
|
||||
fi
|
||||
mkdir -p "$DOWNLOAD_PATH" "$LOG_DIR" "$PID_DIR" 2>/dev/null || true
|
||||
mkdir -p "$DOWNLOAD_PATH" "$LOG_DIR" "$PID_DIR" "$STATE_DIR" 2>/dev/null || true
|
||||
}
|
||||
|
||||
check_dependencies() {
|
||||
@@ -330,6 +347,8 @@ load_job() {
|
||||
MAX_DURATION=""
|
||||
EXTRA_FFMPEG_ARGS=""
|
||||
SCHEDULE_ENABLED="false"
|
||||
SCHEDULE_ONCE="false"
|
||||
SCHEDULE_DATE=""
|
||||
SCHEDULE_DAYS="*"
|
||||
SCHEDULE_START=""
|
||||
SCHEDULE_STOP=""
|
||||
@@ -351,6 +370,27 @@ load_job() {
|
||||
log "ERROR" "STREAM_URL fehlt in: ${job_file}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "${SCHEDULE_ONCE:-false}" == "true" ]]; then
|
||||
if [[ -z "${SCHEDULE_DATE:-}" ]]; then
|
||||
log "ERROR" "SCHEDULE_DATE fehlt in einmaligem Zeitplan: ${job_file}"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! "${SCHEDULE_DATE}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
|
||||
log "ERROR" "Ungültiges SCHEDULE_DATE in ${job_file}: ${SCHEDULE_DATE} (erwartet: YYYY-MM-DD)"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$MAX_DURATION" && "$MAX_DURATION" != *:* ]]; then
|
||||
local normalized_duration
|
||||
normalized_duration=$(parse_duration "$MAX_DURATION")
|
||||
if [[ -z "$normalized_duration" ]]; then
|
||||
log "ERROR" "Ungültige MAX_DURATION in ${job_file}: ${MAX_DURATION}"
|
||||
return 1
|
||||
fi
|
||||
MAX_DURATION="$normalized_duration"
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_job_path() {
|
||||
@@ -377,6 +417,44 @@ is_recording() {
|
||||
return 1
|
||||
}
|
||||
|
||||
schedule_state_file() {
|
||||
local job_id="$1"
|
||||
echo "${STATE_DIR}/${job_id}.last-scheduled"
|
||||
}
|
||||
|
||||
schedule_occurrence_key() {
|
||||
local schedule_date
|
||||
if [[ "${SCHEDULE_ONCE:-false}" == "true" ]]; then
|
||||
schedule_date="${SCHEDULE_DATE:-$(date '+%F')}"
|
||||
else
|
||||
schedule_date=$(date '+%F')
|
||||
fi
|
||||
echo "${SCHEDULE_ONCE:-false}|${schedule_date}|${SCHEDULE_DAYS:-*}|${SCHEDULE_START:-}|${SCHEDULE_STOP:-}|${MAX_DURATION:-}"
|
||||
}
|
||||
|
||||
schedule_already_started() {
|
||||
local job_id="$1"
|
||||
local occurrence_key="$2"
|
||||
local state_file
|
||||
state_file=$(schedule_state_file "$job_id")
|
||||
|
||||
[[ -f "$state_file" ]] || return 1
|
||||
[[ "$(cat "$state_file" 2>/dev/null || true)" == "$occurrence_key" ]]
|
||||
}
|
||||
|
||||
mark_schedule_started() {
|
||||
local job_id="$1"
|
||||
local occurrence_key="$2"
|
||||
local state_file
|
||||
state_file=$(schedule_state_file "$job_id")
|
||||
|
||||
mkdir -p "$(dirname "$state_file")" 2>/dev/null || true
|
||||
if ! printf '%s\n' "$occurrence_key" > "$state_file"; then
|
||||
log "ERROR" "Scheduler-Status konnte nicht geschrieben werden: ${state_file}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================
|
||||
# Aufnahme
|
||||
# =============================================
|
||||
@@ -461,9 +539,15 @@ record_stream() {
|
||||
_SR_FFMPEG_PID=""
|
||||
|
||||
if [[ $ret -eq 0 ]]; then
|
||||
if [[ -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"
|
||||
else
|
||||
[[ -f "$output_file" ]] && rm -f "$output_file"
|
||||
log "ERROR" "Aufnahme fehlgeschlagen: Ausgabedatei fehlt oder ist leer (${output_file})"
|
||||
notify "error" "Aufnahme fehlgeschlagen" "${STREAM_NAME}: Ausgabedatei fehlt oder ist leer" "high"
|
||||
fi
|
||||
break
|
||||
fi
|
||||
|
||||
@@ -519,7 +603,7 @@ record_all() {
|
||||
[[ -f "$job_file" ]] || continue
|
||||
found=true
|
||||
log "INFO" "Starte Job im Hintergrund: $(basename "$job_file")"
|
||||
"$0" ${CONFIG_FILE:+-c "$CONFIG_FILE"} record "$job_file" &
|
||||
"$0" ${CONFIG_FILE:+-c "$CONFIG_FILE"} record-run "$job_file" &
|
||||
done
|
||||
|
||||
if ! $found; then
|
||||
@@ -532,16 +616,43 @@ record_all() {
|
||||
log "INFO" "Alle Aufnahmen beendet"
|
||||
}
|
||||
|
||||
start_detached_recording() {
|
||||
local job_file="$1"
|
||||
|
||||
load_job "$job_file" || return 1
|
||||
|
||||
local job_id
|
||||
job_id=$(basename "$job_file" .job)
|
||||
|
||||
if is_recording "$job_id"; then
|
||||
log "WARN" "Aufnahme von '${STREAM_NAME}' läuft bereits (Job: ${job_id})"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local nohup_log="${LOG_DIR}/nohup_${job_id}.log"
|
||||
log "INFO" "Starte Aufnahme im Hintergrund: ${STREAM_NAME} [${job_id}]"
|
||||
|
||||
nohup setsid "$0" ${CONFIG_FILE:+-c "$CONFIG_FILE"} record-run "$job_file" \
|
||||
>> "$nohup_log" 2>&1 < /dev/null &
|
||||
local detached_pid=$!
|
||||
disown 2>/dev/null || true
|
||||
|
||||
echo "Aufnahme im Hintergrund gestartet: ${STREAM_NAME} [${job_id}]"
|
||||
echo "Starter-PID: ${detached_pid}"
|
||||
echo "Logs: ${LOG_DIR}/stream-recorder.log"
|
||||
}
|
||||
|
||||
# =============================================
|
||||
# Scheduler (wird per systemd-Timer aufgerufen)
|
||||
# =============================================
|
||||
|
||||
run_scheduler() {
|
||||
local today_num now_time
|
||||
local today_date today_num now_time
|
||||
today_date=$(date '+%F')
|
||||
today_num=$(date '+%u')
|
||||
now_time=$(date '+%H:%M')
|
||||
|
||||
log "DEBUG" "Scheduler: Tag=${today_num} Zeit=${now_time}"
|
||||
log "DEBUG" "Scheduler: Datum=${today_date} Tag=${today_num} Zeit=${now_time}"
|
||||
|
||||
for job_file in "${JOBS_DIR}"/*.job; do
|
||||
[[ -f "$job_file" ]] || continue
|
||||
@@ -554,7 +665,11 @@ run_scheduler() {
|
||||
[[ -z "${SCHEDULE_START:-}" ]] && continue
|
||||
|
||||
local is_today=false
|
||||
if [[ "${SCHEDULE_ONCE:-false}" == "true" ]]; then
|
||||
[[ "${SCHEDULE_DATE:-}" == "$today_date" ]] && is_today=true
|
||||
else
|
||||
_matches_schedule_day "$today_num" "${SCHEDULE_DAYS:-*}" && is_today=true
|
||||
fi
|
||||
|
||||
local in_window=false
|
||||
if $is_today; then
|
||||
@@ -569,11 +684,20 @@ run_scheduler() {
|
||||
fi
|
||||
|
||||
if $in_window; then
|
||||
local occurrence_key
|
||||
occurrence_key=$(schedule_occurrence_key)
|
||||
|
||||
if schedule_already_started "$job_id" "$occurrence_key"; then
|
||||
log "DEBUG" "Scheduler: '${STREAM_NAME}' [${job_id}] wurde für dieses Zeitfenster bereits gestartet"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! is_recording "$job_id"; then
|
||||
log "INFO" "Scheduler: Starte '${STREAM_NAME}' [${job_id}]"
|
||||
notify "start" "Geplante Aufnahme" "${STREAM_NAME} - gestartet durch Scheduler"
|
||||
touch "${PID_DIR}/${job_id}.scheduled"
|
||||
nohup setsid "$0" ${CONFIG_FILE:+-c "$CONFIG_FILE"} record "$job_file" \
|
||||
mark_schedule_started "$job_id" "$occurrence_key" || continue
|
||||
printf '%s\n' "$occurrence_key" > "${PID_DIR}/${job_id}.scheduled"
|
||||
nohup setsid "$0" ${CONFIG_FILE:+-c "$CONFIG_FILE"} record-run "$job_file" \
|
||||
>> "${LOG_DIR}/nohup_${job_id}.log" 2>&1 &
|
||||
disown 2>/dev/null || true
|
||||
fi
|
||||
@@ -707,15 +831,25 @@ create_job() {
|
||||
echo "--- Zeitplanung (optional) ---"
|
||||
read -rp "Zeitplan aktivieren? [j/N] " sched_input
|
||||
local sched_enabled="false"
|
||||
local sched_days="*" sched_start="" sched_stop=""
|
||||
local sched_once="false" sched_date="" sched_days="*" sched_start="" sched_stop=""
|
||||
if [[ "$sched_input" =~ ^[JjYy]$ ]]; then
|
||||
sched_enabled="true"
|
||||
read -rp "Einmaliger Termin statt wöchentlich? [j/N] " sched_once_input
|
||||
if [[ "$sched_once_input" =~ ^[JjYy]$ ]]; then
|
||||
sched_once="true"
|
||||
read -rp "Datum (YYYY-MM-DD) [$(date +%F)]: " sched_date_input
|
||||
sched_date="${sched_date_input:-$(date +%F)}"
|
||||
else
|
||||
read -rp "Tage [*] (z.B. Mo-Fr, Sa,So, *): " sched_days_input
|
||||
sched_days="${sched_days_input:-*}"
|
||||
fi
|
||||
read -rp "Startzeit (HH:MM): " sched_start
|
||||
if [[ -z "$sched_start" ]]; then
|
||||
echo "Keine Startzeit angegeben - Zeitplan deaktiviert."
|
||||
sched_enabled="false"
|
||||
elif [[ "$sched_once" == "true" && -z "$sched_date" ]]; then
|
||||
echo "Kein Datum angegeben - Zeitplan deaktiviert."
|
||||
sched_enabled="false"
|
||||
else
|
||||
read -rp "Endzeit (HH:MM, leer=keine): " sched_stop
|
||||
fi
|
||||
@@ -753,7 +887,11 @@ create_job() {
|
||||
echo " Format: ${ext:-auto}"
|
||||
echo " Dauer: ${duration:-unbegrenzt}"
|
||||
if [[ "$sched_enabled" == "true" ]]; then
|
||||
echo " Zeitplan: ${sched_days} ${sched_start}${sched_stop:+ - ${sched_stop}}"
|
||||
if [[ "$sched_once" == "true" ]]; then
|
||||
echo " Zeitplan: einmalig ${sched_date} ${sched_start}${sched_stop:+ - ${sched_stop}}"
|
||||
else
|
||||
echo " Zeitplan: wöchentlich ${sched_days} ${sched_start}${sched_stop:+ - ${sched_stop}}"
|
||||
fi
|
||||
else
|
||||
echo " Zeitplan: deaktiviert"
|
||||
fi
|
||||
@@ -782,6 +920,8 @@ EXTRA_FFMPEG_ARGS=""
|
||||
###############################################
|
||||
|
||||
SCHEDULE_ENABLED=${sched_enabled}
|
||||
SCHEDULE_ONCE=${sched_once}
|
||||
SCHEDULE_DATE="${sched_date}"
|
||||
SCHEDULE_DAYS="${sched_days}"
|
||||
SCHEDULE_START="${sched_start}"
|
||||
SCHEDULE_STOP="${sched_stop}"
|
||||
@@ -832,7 +972,11 @@ list_jobs() {
|
||||
|
||||
local extra_info=""
|
||||
if [[ "${SCHEDULE_ENABLED:-false}" == "true" && -n "${SCHEDULE_START:-}" ]]; then
|
||||
extra_info="Zeitplan: ${SCHEDULE_DAYS:-*} ${SCHEDULE_START}"
|
||||
if [[ "${SCHEDULE_ONCE:-false}" == "true" ]]; then
|
||||
extra_info="Zeitplan: einmalig ${SCHEDULE_DATE:-?} ${SCHEDULE_START}"
|
||||
else
|
||||
extra_info="Zeitplan: wöchentlich ${SCHEDULE_DAYS:-*} ${SCHEDULE_START}"
|
||||
fi
|
||||
if [[ -n "${SCHEDULE_STOP:-}" ]]; then
|
||||
extra_info+=" - ${SCHEDULE_STOP}"
|
||||
elif [[ -n "${MAX_DURATION:-}" ]]; then
|
||||
@@ -987,6 +1131,7 @@ Befehle:
|
||||
record-all Alle Jobs aufzeichnen
|
||||
create Neuen Job interaktiv erstellen
|
||||
quick Interaktive Schnellaufnahme (URL eingeben)
|
||||
upload <datei> Vorhandene Datei nach Plik hochladen
|
||||
stop <name> Aufnahme stoppen
|
||||
stop-all Alle Aufnahmen stoppen
|
||||
list Jobs und deren Zeitpläne anzeigen
|
||||
@@ -997,6 +1142,7 @@ Befehle:
|
||||
Optionen:
|
||||
-c, --config <datei> Alternative Konfigurationsdatei
|
||||
-V, --verbose Ausführliche Ausgabe
|
||||
--foreground Aufnahme im Vordergrund ausführen
|
||||
-h, --help Diese Hilfe
|
||||
-v, --version Version
|
||||
|
||||
@@ -1021,6 +1167,7 @@ main() {
|
||||
case "$1" in
|
||||
-c|--config) CONFIG_FILE="$2"; shift 2 ;;
|
||||
-V|--verbose) VERBOSE=true; shift ;;
|
||||
--foreground) FOREGROUND=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
-v|--version) echo "stream-recorder v${VERSION}"; exit 0 ;;
|
||||
*) break ;;
|
||||
@@ -1035,6 +1182,19 @@ main() {
|
||||
|
||||
case "$command" in
|
||||
record)
|
||||
if [[ -z "${1:-}" ]]; then
|
||||
log "ERROR" "Job-Datei oder Job-Name erforderlich"
|
||||
echo ""; usage; exit 1
|
||||
fi
|
||||
local job_file
|
||||
job_file=$(resolve_job_path "$1")
|
||||
if [[ "$FOREGROUND" == "true" || "$VERBOSE" == "true" ]]; then
|
||||
record_stream "$job_file"
|
||||
else
|
||||
start_detached_recording "$job_file"
|
||||
fi
|
||||
;;
|
||||
record-run)
|
||||
if [[ -z "${1:-}" ]]; then
|
||||
log "ERROR" "Job-Datei oder Job-Name erforderlich"
|
||||
echo ""; usage; exit 1
|
||||
@@ -1046,6 +1206,12 @@ main() {
|
||||
record-all) record_all ;;
|
||||
create) create_job ;;
|
||||
quick) interactive_record ;;
|
||||
upload)
|
||||
if [[ -z "${1:-}" ]]; then
|
||||
log "ERROR" "Dateipfad erforderlich"; exit 1
|
||||
fi
|
||||
PLIK_ENABLED=true upload_to_plik "$1" "${2:-$(basename "$1")}"
|
||||
;;
|
||||
scheduler) run_scheduler ;;
|
||||
stop)
|
||||
if [[ -z "${1:-}" ]]; then
|
||||
|
||||
Reference in New Issue
Block a user