Release: v2.4.0

This commit is contained in:
Patrick Asmus
2026-09-06 16:58:37 +02:00
parent 808d34a244
commit 2732427721
7 changed files with 301 additions and 66 deletions
+32 -4
View File
@@ -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
+3
View File
@@ -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
+27
View File
@@ -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
+3 -1
View File
@@ -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"
+5 -3
View File
@@ -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=""
+10 -3
View File
@@ -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
+221 -55
View File
@@ -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
log "INFO" "Aufnahme abgeschlossen: ${output_file}"
notify "stop" "Aufnahme abgeschlossen" "${STREAM_NAME}: ${output_file}"
upload_to_plik "$output_file" "$STREAM_NAME"
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
_matches_schedule_day "$today_num" "${SCHEDULE_DAYS:-*}" && is_today=true
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 "Tage [*] (z.B. Mo-Fr, Sa,So, *): " sched_days_input
sched_days="${sched_days_input:-*}"
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