Stream Recorder
Stream Recorder ist ein Shell-Skript zur zeitgesteuerten Aufnahme von TV Livestreams aus dem TV-Browser heraus.
Inhaltsverzeichnis
Voraussetzungen
- Internetanschluß
- Linux Pc
- at (Paket)
- mencoder (Paket)
- TV-Browser - Aufnahmesteuerung Plugin
Installation
Das Skript
Benötigt wird die Installation der Pakete 'at' und 'ffmpeg'.
Der at Dienst wird von systemd gesteuert. Zur Aktivierung gibt man auf der Konsole (sudo systemctl enable --now atd.service)
ein.
Das Skript wird kopiert und z.B. mit dem Namen (streamrecoder)
in einem Ordner mit Benutzerschreibrechten abgespeichert. Danach wird es mit (chmod a+rx streamrecorder)
ausführbar gemacht.
Konfiguration des TV-Browsers
Das Plugin Aufnahmesteuerung muss geladen werden.
Im Tv-Browser Menu Extras -> Aufnahmesteuerung -> Geräte -> Gerät-hinzufügen und den Standardtreiber auswählen. Beim Eintrag "Gerät" kann ein beliebiger Name eingetragen werden.
Als "Applikation" wird das Skript (streamrecoder)
mit seinem Pfad angegeben.
Parameter für die Aufnahme:
record {start_hour} {start_minute} {start_year} {start_month} {start_day} {end_hour} {end_minute} {end_year} {end_month} {end_day} "{channel_name}" "{title}" {production_year} {episode_number} "{episode}"
Parameter für das Löschen:
delete {start_hour} {start_minute} {start_year} {start_month} {start_day} {end_hour} {end_minute} {end_year} {end_month} {end_day} "{channel_name}" "{title}" {production_year} {episode_number} "{episode}"
Bei den Einstellungen können mehere Aufnahme gleichzeitig erlaubt werden.
Auch wird die Aufnahme schon laufender Sendungen unterstützt.
Die Sender URLs der öffentlich rechtlichen Sender sind im Skript gelistet (Stand November 2020). Man kann beliebig viele Sender hinzufügen. Die Namen der Sender sollten im TV-Browser und im Skript miteinander übereinstimmen. Leerstellen im Sendernamen sollten im Skript mit Unterstrich eingetragen werden. Der Speicherort der Aufnahme ist auf ~/Video vor eingestellt und kann im Skript in der Benutzer Konfiguration angepasst werden. Es kann sein, dass ein Sender die URL eines Livestreams im Laufe der Zeit ändert. Eine recht gute Quelle für aktuelle Livestream-URLs bietet das Programm Medithek-View.
Benutzung
Die Benutzung ist im Grunde selbsterklärend. Mit der rechten Maustaste wählt man eine Sendung aus. Mit der Linken Maustaste wird auf Aufnahme gedrückt. Zum Rückgängig machen der Aufnahmejobs geht man entsprechend vor. Ein Dialogfenster von TV-Browser öffnet sich und gibt jeweils Auskunft über den Programmablauf.
Hintergrund
Zur Zeitsteuerung habe ich at ausgewählt. Cronie und systemd.timers sind besser für wiederkehrende Ereignisse geeignet. Wer wissen möchte wie die at-Jobs gespeichert werden, kann auf der Konsole die Befehle atq, at -c <job> und atrm <job> ausprobieren. Für das eigentliche streaming ist mencoder verantwortlich.
Viel Spaß damit tuxnix
Das Skript
#!/bin/bash ##### streamrecorder Version 1.3 - Nov. 2020 ##### From TuxNix Lizenz GPLv2 with no garanty at all. ##### Parameter für Tv-Browser: Aufnahme #record {start_hour} {start_minute} {start_year} {start_month} {start_day} {end_hour} {end_minute} {end_year} {end_month} {end_day} "{channel_name}" "{title}" {production_year} {episode_number} "{episode}" ##### Parameter für Tv-Browser: Löschen #delete {start_hour} {start_minute} {start_year} {start_month} {start_day} {end_hour} {end_minute} {end_year} {end_month} {end_day} "{channel_name}" "{title}" {production_year} {episode_number} "{episode}" ##### benötigt die Pakete #at #ffmpeg # Der at service muss mit dem Befehl: 'sudo systemctl enable --now atd.service' aktiviert werden. ##### Benutzer Konfiguration recPath=/Videos/ ##### Das Script args=("$@") operation=${args[0]} sStunde=${args[1]} if (( ${#sStunde} < 2 )) ; then sStunde="0"${args[1]} fi sMinute=${args[2]} if (( ${#sMinute} < 2 )) ; then sMinute="0"$sMinute fi sJahr=${args[3]} sMonat=${args[4]} if (( ${#sMonat} < 2 )) ; then sMonat="0"$sMonat fi sTag=${args[5]} if (( ${#sTag} < 2 )) ; then sTag="0"$sTag fi eStunde=${args[6]} if (( ${#eStunde} < 2 )) ; then eStunde="0"$eStunde fi eMinute=${args[7]} if (( ${#eMinute} < 2 )) ; then eMinute="0"$eMinute fi eJahr=${args[8]} eMonat=${args[9]} if (( ${#args[9]} < 2 )) ; then eMonat="0"${args[9]} fi eTag=${args[10]} if (( ${#args[10]} < 2 )) ; then eTag="0"${args[10]} fi Kanal="${args[11]}" Kanal=`echo $Kanal | sed 's/ /_/g'` Kanal=`echo $Kanal | sed 's!"!!g'` Titel=${args[12]} if (( ${#args[13]} > 3 )) ; then ProdJahr="-${args[13]}" fi if (( ${#args[14]} > 2 )) ; then EpisodenNr="-${args[14]}" fi if (( ${#args[15]} > 0 )) ; then Episode="-${args[15]}" fi Name="$Titel$ProdJahr$EpisodenNr$Episode" Name=`echo $Name | sed 's/ /_/g'` Name=`echo $Name | sed 's!/!-!g'` Name=`echo $Name | sed 's![()"]!!g'` Name=`echo $Name | sed "s!\x27!!g"` Name=`echo $Name | sed "s!&!u!g"` if [ $Kanal == 'Das_Erste_(ARD)' ] ; then stream_url="https://derste247livede.akamaized.net/hls/live/658317/daserste_de/master.m3u8" elif [ $Kanal == 'ARD-alpha' ] ; then stream_url="https://brlive-lh.akamaihd.net/i/bralpha_germany@119899/master.m3u8" elif [ $Kanal == 'One' ] ; then stream_url="https://onelivestream-lh.akamaihd.net/i/one_livestream@568814/master.m3u8" elif [ $Kanal == 'tagesschau24' ] ; then stream_url="https://tagesschau-lh.akamaihd.net/i/tagesschau_1@119231/master.m3u8" elif [ $Kanal == 'arte' ] ; then stream_url="https://artelive-lh.akamaihd.net/i/artelive_de@393591/master.m3u8" elif [ $Kanal == 'ARTE_(francais)' ] ; then stream_url="https://artelive-lh.akamaihd.net/i/artelive_fr@344805/master.m3u8" elif [ $Kanal == 'BR' ] ; then stream_url="http://livestreams.br.de/i/bfsnord_germany@119898/master.m3u8" elif [ $Kanal == 'BR_Sued' ] ; then stream_url="https://brlive-lh.akamaihd.net/i/bfssued_germany@119890/master.m3u8" elif [ $Kanal == 'BR_Nord' ] ; then stream_url="https://brlive-lh.akamaihd.net/i/bfsnord_germany@119898/master.m3u8" elif [ $Kanal == 'KiKA' ] ; then stream_url="https://kikageohls-i.akamaihd.net/hls/live/1006268/livetvkika_de/master.m3u8" elif [ $Kanal == 'HR' ] ; then stream_url="https://live1_hr-lh.akamaihd.net/i/hr_fernsehen@75910/master.m3u8" elif [ $Kanal == 'MDR_Thueringen' ] ; then stream_url="https://mdrthuhls-lh.akamaihd.net/i/livetvmdrthueringen_de@514027/master.m3u8" elif [ $Kanal == 'MDR_Sachsen' ] ; then stream_url="https://mdrsnhls-lh.akamaihd.net/i/livetvmdrsachsen_de@513998/master.m3u8" elif [ $Kanal == 'MDR_Sachsen_Anhalt' ] ; then stream_url="https://mdrsahls-lh.akamaihd.net/i/livetvmdrsachsenanhalt_de@513999/master.m3u8" elif [ $Kanal == 'NDR_Niedersachsen' ] ; then stream_url="https://ndrfs-lh.akamaihd.net/i/ndrfs_nds@430233/master.m3u8" elif [ $Kanal == 'NDR_Schleswig-Holstein' ] ; then stream_url="https://ndrfs-lh.akamaihd.net/i/ndrfs_sh@430234/master.m3u8" elif [ $Kanal == 'PHOENIX' ] ; then stream_url="https://zdf-hls-19.akamaized.net/hls/live/2016502/de/high/master.m3u8" elif [ $Kanal == 'RBB_Berlin' ] ; then stream_url="https://rbblive-lh.akamaihd.net/i/rbb_berlin@144674/master.m3u8" elif [ $Kanal == 'RBB_Brandenburg' ] ; then stream_url="https://rbblive-lh.akamaihd.net/i/rbb_brandenburg@349369/master.m3u8" elif [ $Kanal == '3sat' ] ; then stream_url="https://zdf-hls-18.akamaized.net/hls/live/2016501/dach/high/master.m3u8" elif [ $Kanal == 'SWR_BW' ] ; then stream_url="https://swrbwhls-i.akamaihd.net/hls/live/667638/swrbwd/master.m3u8" elif [ $Kanal == 'SWR_SR' ] ; then stream_url="https://live2_sr-lh.akamaihd.net/i/sr_universal02@107595/master.m3u8" elif [ $Kanal == 'SWR_RP' ] ; then stream_url="https://swrrphls-i.akamaihd.net/hls/live/667639/swrrpd/master.m3u8" elif [ $Kanal == 'WDR' ] ; then stream_url="https://wdrfsgeo-lh.akamaihd.net/i/wdrfs_geogeblockt@530016/master.m3u8" elif [ $Kanal == 'ZDF' ] ; then stream_url="https://zdf-hls-15.akamaized.net/hls/live/2016498/de/high/master.m3u8" elif [ $Kanal == 'ZDFinfo' ] ; then stream_url="https://zdf-hls-17.akamaized.net/hls/live/2016500/de/high/master.m3u8" elif [ $Kanal == 'ZDFneo' ] ; then stream_url="https://zdf-hls-16.akamaized.net/hls/live/2016499/de/high/master.m3u8" #Geoblocking (In der BRD nur mittels VPN zu streamen) elif [ $Kanal == 'ORF_eins' ] ; then stream_url="https://orf1.mdn.ors.at/out/u/orf1/qxb/manifest.m3u8" elif [ $Kanal == 'ORF_2' ] ; then stream_url="https://orf2e.mdn.ors.at/out/u/orf2e/qxb/manifest.m3u8" elif [ $Kanal == 'ORF_III' ] ; then stream_url="https://orf3.mdn.ors.at/out/u/orf3/qxb/manifest.m3u8" elif [ $Kanal == 'ORF_SPORT_PLUS' ] ; then stream_url="https://orfs.mdn.ors.at/out/u/orfs/qxb/manifest.m3u8" else echo "Abbruch: Der Sender '$Kanal' ist im Aufnahmeskript nicht gelistet." echo "" echo "Die Namen der Sender im TV-Browser müssen mit denen im Skript übereinstimmen." echo "Bitte Groß und Kleinschreibung beachten und Leerzeichen im Sendernamen im Skript durch Unterstrich ersetzen." echo "Umlaute werden nicht unterstützt." exit 1 fi sysTime=`date --date='now' +%s` startTime=`date --date="$sJahr-$sMonat-$sTag $sStunde:$sMinute" +%s` endTime=`date --date="$eJahr-$eMonat-$eTag $eStunde:$eMinute" +%s` startNow=`date --date='now' +%R' '%F` stopNow=`date --date="1 minute" +%R' '%F` startZeit=$sStunde:$sMinute' '$sJahr-$sMonat-$sTag endZeit=$eStunde:$eMinute' '$eJahr-$eMonat-$eTag if [ $operation == 'record' ] && (( $sysTime >= $endTime )) ; then echo "" echo "Die Sendung ist abgelaufen und kann nicht mehr aufgenommen werden." exit 1 elif [ $operation == 'record' ] && (( $sysTime > $startTime )) ; then echo "`date --date='now' +%R` - $eStunde:$eMinute Uhr $eTag.$eMonat.$eJahr: $Titel" echo "" echo "Die Aufnahme wurde gestartet und wird nach" echo $recPath$Name"-part.mp4 gespeichert." echo "ffmpeg -i $stream_url -vcodec copy -acodec copy $recPath$Name-part.mp4" | at $startNow > /dev/null 2>&1 #echo "mpv $stream_url --stream-record=$recPath$Name-part.mp4" | at $startNow > /dev/null 2>&1 #echo "mencoder $stream_url -vid 9 -oac pcm -ovc copy -o $recPath$Name-part.mp4" | at $startNow > /dev/null 2>&1 echo "fuser -k -TERM $recPath$Name-part.mp4" | at $endZeit > /dev/null 2>&1 elif [ $operation == 'record' ] && (( $sysTime <= $startTime )) ; then echo "$sStunde:$sMinute - $eStunde:$eMinute Uhr $eTag.$eMonat.$eJahr: $Titel" echo "" echo "Die Aufnahme wird veranlasst und nach" echo $recPath$Name".mp4 gespeichert." #echo "ffmpeg -i $stream_url -vcodec copy -acodec copy $recPath$Name.mp4" echo "ffmpeg -i $stream_url -vcodec copy -acodec copy $recPath$Name.mp4" | at $startZeit > /dev/null 2>&1 echo "fuser -k -TERM $recPath$Name.mp4" | at $endZeit > /dev/null 2>&1 elif [ $operation == 'delete' ] ; then Monate=([1]=Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) if (( $sysTime >= $startTime )) ; then echo "fuser -k -TERM $recPath$Name.mp4" | at $stopNow > /dev/null 2>&1 echo "fuser -k -TERM $recPath$Name-part.mp4" | at $stopNow > /dev/null 2>&1 echo "Die laufende Aufnahme von '$Titel' wird beendet!" else sTag=${args[5]} if (( ${#sTag} < 2 )) ; then sTag=" "$sTag fi atq | grep "${Monate[${args[4]}]} $sTag $sStunde:$sMinute:00 $sJahr" > atq.txt Jobs=`cut -f1 atq.txt` for Job in $Jobs ; do if at -c $Job | grep $Name > /dev/null 2>&1 ; then atrm $Job echo "Der at Start-Job $Job wird gelöscht!" fi done fi eTag=${args[10]} if (( ${#eTag} < 2 )) ; then eTag=" "$eTag fi atq | grep "${Monate[${args[9]}]} $eTag $eStunde:$eMinute:00 $eJahr" > atq.txt Jobs=`cut -f1 atq.txt` for Job in $Jobs ; do if at -c $Job | grep $Name > /dev/null 2>&1 ; then atrm $Job echo "Der at Stop-Job $Job wird gelöscht!" fi done else echo "Abbruch: Der Operator $operation ist nicht korrekt. Bitte überprüfe die Parameter im TV-Browser Aufnahme Plugin" exit 2 fi
Anregungen und Verbesserungsvorschläge zum Skript können im Forum ausgetauscht werden.