Pavion / tvstreamrecord

Timed TV stream recording for Python
GNU General Public License v3.0
45 stars 10 forks source link

Zeitgleiche Aufnahmen am FritzRepeater #33

Open danielk117 opened 5 years ago

danielk117 commented 5 years ago

Hallo,

wie ich bereits in dem Issue https://github.com/Pavion/tvstreamrecord/issues/22 lesen konnte, hat tvstreamrecord keine Begrenzung für zeitgleiche Aufnahmen. Die DVB-C fähigen Geräte von AVM hingegen streamen an ein Gerät aber immer nur einmal.

Ich habe tvstreamrecord auf einer DS415+ laufen. Diese hat zwei Ethernet-Interfaces, welche beide an meiner FritzBox hängen. Zusätzlich habe ich im Netz den FritzRepeater mit DVB-C. Das gleichzeitige Aufnehmen wäre also theoretisch möglich, da ich über zwei verschiedene IPs auf den Repeater zugreifen kann. Funktionieren tut das allerdings mit tvstreamrecord noch nicht. Wahrscheinlich deshalb, weil beide ffmpeg-Prozesse ihre Verbindung über das selbe Interface aufbauen wollen.

Gibt es hier eine Möglichkeit das Problem mit ffmpeg selbst zu lösen oder wäre da eine Lösung über tvstreamrecord möglich? Ein Beispiel: Wenn bereits eine Aufnahme läuft, dann sorgt tvstreamrecord dafür, dass der zweite ffmpeg-Prozess über das zweite Interface arbeitet.

Ich stelle mich auch gern als Versuchskaninchen zur Verfügung.

Beste Grüße Daniel

Pavion commented 5 years ago

Hallo Daniel,

es ist an sich eine interessante Idee, allerdings ist es eher eine Linux-/Netzwerk-Frage und da kenne ich mich nicht so gut aus. Zudem sind mehrere LANs eher für Link Aggregation gedacht, das ist wieder eine andere Geschichte... Was ich aber gerade gelesen habe, sieht in etwa so aus:

Die Regel, welches Netzwerkadapter verwendet wird (route), kann nur in Linux direkt eingestellt werden; https://superuser.com/questions/1355077/ffmpeg-choose-outbound-ip-eth0-or-eth1 Das sehe dann in etwa so aus:

root@DiskStation:~# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
IP1             0.0.0.0         255.255.255.0   U     0      0        0 eth0
IP2             0.0.0.0         255.255.255.0   U     0      0        0 eth1

Diese Route muss aber eigentlich an die Empfänger-Adresse (IP1/IP2) geknüpft werden. Ob man dem Repeater zwei Adressen beibringen kann, wage ich jedoch zu bezweifeln.

Weitere wage Ideen:

Also nach einer Stunde Recherche fand ich leider nichts, was ich umsetzen oder gar testen könnte. Wenn Du weiterführende Ideen hast, melde Dich gern wieder.

Gruß Pav

danielk117 commented 5 years ago

Hallo Pavion,

danke für deine Ideen, die Docker-Variante wäre mein letzter Ausweg. Ich habe bereits zwei Möglichkeit erfolgreich testen können. Allerdings gelingt mir in beiden Fällen immer nur der direkte Aufruf von ffmpeg über die Kommandozeile.

Variante 1 https://superuser.com/questions/241178/how-to-use-different-network-interfaces-for-different-processes Über netns einen Interface-Namespace erzeugen, in welchem dann ein ffmpeg-Prozess läuft.

sudo ip netns add eth1_ns
sudo ip link set eth1 netns eth1_ns
sudo ip netns exec eth1_ns sudo ifconfig eth1 10.1.1.10/24 up
sudo ip netns exec eth1_ns sudo ifconfig lo 127.0.0.1/8 up
sudo ip netns exec eth1_ns sudo route add default gw 10.1.1.1
sudo ip netns exec eth1_ns sudo dhclient eth1
sudo ip netns exec eth1_ns sudo /volume1/@appstore/VideoStation/bin/ffmpeg XXXXXXXXXX

Funktioniert, wenn komplett über die Kommandozeile ausgeführt. Anschließend: Alle Schritte bis zum dhclient habe ich direkt über die Kommandozeile gemacht. Den letzten Punkt wollte ich dann über tvstreamrecord starten. Egal ob in der tvstreamrecord.py oder über die Config (ffmpeg-Pfad), ich bekomme immer Meldungen:

2019-02-09 15:15:44.754 | FFMPEG could not be started. Error: [Errno 2] No such file or directory
2019-02-09 15:15:44.745 | [u'sudo  ip netns exec eth1_ns sudo /volume1/@appstore/VideoStation/bin/ffmpeg',  u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=346&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,1400,1401,1402,1403,1404,1406,1405,1470,1476,2171',  u'-y', u'-t', u'675', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190209151544 -  dasbloghaus_tv___Grafitti_for_Love.ts']

Variante 2 https://daniel-lange.com/archives/53-Binding-applications-to-a-specific-IP.html IP-Adresse des anderen Interfaces angeben und über das bind.so-Skript ausführen.

BIND_ADDR="192.168.170.45" LD_PRELOAD=/usr/lib/bind.so sudo /volume1/@appstore/VideoStation/bin/ffmpeg XXXXXXXXXXXXX

Auch das funktioniert problemlos über Kommandozeile. Doch dann die selben Probleme wie oben. Über tvstreamrecord geht nix...

2019-02-09 18:18:47.514 | FFMPEG could not be started. Error: [Errno 2] No such file or directory
2019-02-09 18:18:47.506 | [u'BIND_ADDR="192.168.170.45"  LD_PRELOAD=/usr/lib/bind.so  /volume1/@appstore/VideoStation/bin/ffmpeg', u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=314&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,200,210,220,221,225,222,230,231,250',  u'-y', u'-t', u'2592', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190209181847 -  Bl_tentr_ume.ts']

Es scheint sich um irgendein Rechte-Problem zu handeln. Kannst du vielleicht weiterhelfen?

VG Daniel

Pavion commented 5 years ago

Nein. Nur die Leerzeichen im Befehl werden falsch interpretiert. Wenn Du ihn in eine .sh packst, sollte es funktionieren. Schaue ich mir morgen genauer an.

danielk117 commented 5 years ago

Die Idee hatte ich auch schon...

Variante 3 Ein Skript welches ich in der Config als Pfad zu ffmpeg angebe. Kompletter Pfad für ffmpeg sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh

Das Skript wiederum startet ffmpeg in der bind-Variante:

#!/bin/sh
str="'$*'"
BIND_ADDR="192.168.170.45" LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg "$str"

Und was soll ich sagen, das Resultat bleibt leider das selbe:

2019-02-09 18:43:14.081 | FFMPEG could not be started. Error: [Errno 2] No such file or directory
2019-02-09 18:43:14.073 | [u'sh  /volume1/homes/admin/ffmpegbinder.sh', u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=314&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,200,210,220,221,225,222,230,231,250',  u'-y', u'-t', u'1125', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190209184314 -  Bl_tentr_ume.ts']

Welche Leerzeichen meinst du?

Pavion commented 5 years ago

Leerzeichen zwischen sh und /volume1 Vielleicht ./xxx.sh?

danielk117 commented 5 years ago

Leerzeichen war ein gutes Stichwort...

Folgendes hab ich an der tvstreamrecord.py angepasst:

attr = [config.cfg_ffmpeg_path,"-i", ('"'+self.url+'"'), '-y', '-t', deltasec] + ffargs + [('"'+fn+'"')]
attr = " ".join(attr)

und

self.process = subprocess.Popen(attr, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

In der Config als ffmpeg-Pfad:

BIND_ADDR="192.168.170.45"  LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg

Uns siehe da...

2019-02-10 00:04:26.290 | Record: Stopflag for 'Sportschau' received
2019-02-10 00:03:18.580 | FFMPEG (rtsp) record 'Sportschau' called with:
2019-02-10 00:03:18.580 | BIND_ADDR="192.168.170.45"   LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg  -i  "rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171"  -y -t 821 -loglevel fatal -acodec copy -vcodec copy  "/volume2/Aufnahme/20190210000318 - Sportschau.ts"

Und in der Fritz steht auch tatsächlich die 192.168.170.45 (mein eth1) als zugreifende IP da... Ich werde das morgen noch ein wenig erweitern und dynamisch machen. Immerhin will ich diesen Modus ja nur für eine zeitgleiche Aufnahme. Theoretisch könnte man diese Methode auch für virtuelle Ethernet-Interfaces nutzen. Man bräuchte also nicht zwingend zusätzliche echte Interfaces... Gut für FritzBoxen mit 4 Tunern. Mein Repeater hat leider nur 2 ;-)

danielk117 commented 5 years ago

So, neue Erkenntnisse und Probleme... Änderungen an der tvstreamrecord.py wie in meinem letzten Kommentar.

In der Config als ffmpeg-Pfad:

sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh

Mein ffmpegbinder.sh-Skript sieht so aus:

#!/bin/sh
for i in "192.168.170.21" "192.168.170.45"
do
  runningfile="/volume1/@appstore/tvstreamrecord/running_$i.run"
  if [ -f $runningfile ]; then
    echo "file $runningfile exists"
  else
    echo "file $runningfile not exists"
    echo "running now">$runningfile
    echo "using bind adress $i"
    BIND_ADDR="$i" LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg "$@"
    rm -- "$runningfile"
    break
  fi
done

Zeitgleiche Aufnahmen funktionieren einwandfrei. Aber das Beenden von Aufnahmen klappt nicht... :-( Anscheinend wird das Skript wegen shell=True nicht als Child-Prozess, sondern als eigener Prozess ausgeführt. Somit greifen terminate() oder kill() nicht.

Pavion commented 5 years ago

So, jetzt bin ich wach und wieder am PC :)

Ich sehe, Du hast schon sehr viel Arbeit erledigt und sogar die richtige Stelle in meinem Code gefunden! Leider kann ich nicht wirklich viel davon testen, da ich nicht mal einen Tuner mehr habe, daher bleibe ich erstmal bei Theorie und freue mich auf eine Diskussion.

Durch die Verwendung von shell=True wird die Anwendung noch unsicherer, da damit absolut alle möglichen Befehle mit maximalen Admin-Rechten auf DS ausgeführt werden könnten... Dabei gibt es sogar Leute, die das Tool ohne Passwort im Internet freigeben oO

Eigentlich würde ich, wenn, den Pfad zuerst splitten und auf shell=True verzichten:

attr = shlex.split(config.cfg_ffmpeg_path) + ["-i", self.url, '-y', '-t', deltasec] + ffargs + [fn]

Bin mir aber nicht sicher, ob es mit Deinem Befehl funktioniert. Vielleicht könntest Du das testen? Würde es funktionieren, wenn Dein Befehl in einer .sh stünde?

Was die Implementierung der Schaltung angeht, fallen mir zwei Wege ein:

1. Über eine .sh

Eine Umschaltung in einer .sh realisieren (sorry für mein Linux, ist nicht meine Muttersprache ^^):

if [ -f "flag1" ]
then
touch flag2
BIND2
rm flag2
else
touch flag1
BIND1
rm flag1
fi

Wenn Du Dich darin auskennst, könnte ich mir vorstellen, dass so etwas auf mehr Interfaces erweitert und parametrisiert werden könnte. Ggf. könnte man die vorher erforderliche Einrichtung ebenfalls in die gleiche Datei packen und irgendwie nur bei Bedarf (oder erstem Start) ausführen. Das Ziel wäre eine Art Mega-Script, den ein Bediener auf DS downloaden, [ggf. geringfügig anpassen], seinen Pfad in TSR eintragen und sofort nutzen kann. Oder ist es zu viel gehofft?!

2. über TSR

Einführung und Verarbeitung eines neuen Parameters analog zu "Alternate URL": https://github.com/Pavion/tvstreamrecord/blob/f5d7d1bb0973b2d06986f16bd0f3a3a9caf279cc/tvstreamrecord.py#L1122 Vorteil: bequem anzupassen Nachteil: dadurch, dass dennoch einiges auf Linux-Ebene gemacht wird, ist der Vorteil geringer, dafür aber mehr Code und Optionen. Nicht dass ich die Arbeit von mir schieben würde ^^

Pavion commented 5 years ago

Ich habe schon seit einer Stunde an meiner Nachricht geschrieben und wurde gerade fertig, als Du Deine abgeschickt hast...

Pavion commented 5 years ago

Ich sehe, wir kamen auf ähnliche Ideen... Du hast schon den Script und ich habe vielleicht die Lösung für Dein Shell-Problem 🙂

danielk117 commented 5 years ago

Sehr gut, fast gleichzeitig geantwortet :D

danielk117 commented 5 years ago

Ich würde deine split-Methode mal testen und shell=True weglassen.

danielk117 commented 5 years ago

Also split-Methode ohne shell=True und als ffmpeg-Pfad sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh funktioniert:

2019-02-10 10:39:05.105 | Record: Stopflag for 'NZZ Standpunkte - Die Hydra hebt ihr Haupt - der neue Judenhass' received
2019-02-10 10:38:45.247 | Record: Stopflag for 'Pauline am Strand' received
2019-02-10 10:37:36.328 | FFMPEG (rtsp) record 'Pauline am Strand' called with:
2019-02-10 10:37:36.328 | ['sh',  '/volume1/@appstore/VideoStation/bin/ffmpegbinder.sh', u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=362&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,400,401,402,403,407,408,404,470,1276,2171',  u'-y', u'-t', u'2063', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190210103736 -  Pauline_am_Strand.ts']
2019-02-10 10:37:36.326 | total records: 2 // running records: 2
2019-02-10 10:36:49.971 | FFMPEG (rtsp) record 'NZZ Standpunkte - Die Hydra hebt ihr Haupt - der neue Judenhass' called with:
2019-02-10 10:36:49.971 | ['sh',  '/volume1/@appstore/VideoStation/bin/ffmpegbinder.sh', u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=314&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,200,210,220,221,225,222,230,231,250',  u'-y', u'-t', u'1510', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190210103649 -  NZZ_Standpunkte___Die_Hydra_hebt_ihr_Haupt___der_neue_Judenhass.ts']
2019-02-10 10:36:49.969 | total records: 1 // running records: 1

Beenden funktioniert allerdings trotzdem nicht...

Pavion commented 5 years ago

Hm. Ich nutze ja terminate(), da sollte nix mehr laufen. Kann es sein, dass Dein Script durch sh im separaten Thread gestartet wird? Geht es nicht mit ./ffmpegbinder.sh?

danielk117 commented 5 years ago

Ich vermute auch das mein Skript nicht als Kind-Prozess läuft. Mal gucken warum das so ist...

danielk117 commented 5 years ago

Also, das Skript wird als Kind ausgeführt...

$ sudo pstree -p
        ├─python(26950)─┬─sh(12510)───ffmpeg(12512)
        │               ├─{python}(26960)
        │               ├─{python}(26961)
        │               ├─{python}(26962)
        │               ├─{python}(26963)
        │               ├─{python}(26964)
        │               ├─{python}(26965)
        │               ├─{python}(26966)
        │               ├─{python}(26967)
        │               ├─{python}(26968)
        │               ├─{python}(26969)
        │               ├─{python}(1998)
        │               ├─{python}(12509)
        │               └─{python}(12511)
$ sudo ps -ef | grep ffmpeg && sudo ps -ef | grep python
root     26950     1  0 10:35 ?        00:00:24 python tvstreamrecord.py
root     12510 26950  0 13:14 ?        00:00:00 sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 18169 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210131410 - Sportschau.ts
root     12512 12510  2 13:14 ?        00:00:09 /volume1/@appstore/VideoStation/bin/ffmpeg -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 18169 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210131410 - Sportschau.ts
danielk117 commented 5 years ago

Nach dem Beenden der Aufnahmen über tvstreamrecord ist nur noch der ffmpeg-Prozess da. Somit wird das Bash-Skript anscheinend korrekt beendet.

root     20015     1  3 13:33 ?        00:00:01 /volume1/@appstore/VideoStation/bin/ffmpeg -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 17011 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210133328 - Sportschau.ts
Pavion commented 5 years ago

aber muss man wirklich sh vorschreiben und extra shell erzeugen?

Pavion commented 5 years ago

Mein .terminate() sendet SIGTERM nur an parent: https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash Wenn es ohne .sh nicht funktioniert, könnte das die Lösung sein.

danielk117 commented 5 years ago

So, funktioniert auch ohne sh:

2019-02-10 13:59:13.147 | ['/volume1/@appstore/VideoStation/bin/ffmpegbinder.sh',  u'-i',  'rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171',  u'-y', u'-t', u'15466', u'-loglevel', u'fatal', u'-acodec', u'copy',  u'-vcodec', u'copy', u'/volume2/Aufnahme/20190210135913 -  Sportschau.ts']
2019-02-10 13:59:13.146 | FFMPEG (rtsp) record 'Sportschau' called with:
$ sudo ps -ef | grep ffmpeg
root     19994     1  0 13:33 ?        00:00:08 python tvstreamrecord.py
root     30193 19994  0 13:59 ?        00:00:00 /bin/sh /volume1/@appstore/VideoStation/bin/ffmpegbinder.sh -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 15466 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210135913 - Sportschau.ts
root     30195 30193  3 13:59 ?        00:00:00 /volume1/@appstore/VideoStation/bin/ffmpeg -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 15466 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210135913 - Sportschau.ts

Aber nach dem Beenden:

$ sudo ps -ef | grep ffmpeg
root     30193 19994  0 13:59 ?        00:00:00 [ffmpegbinder.sh] <defunct>
root     30195     1  2 13:59 ?        00:00:04 /volume1/@appstore/VideoStation/bin/ffmpeg -i rtsp://192.168.170.43:554/?avm=1&freq=306&bw=8&msys=dvbc&mtype=64qam&sr=6900&specinv=1&pids=0,16,17,18,20,100,101,102,103,104,106,84,105,1176,2070,2171 -y -t 15466 -loglevel fatal -acodec copy -vcodec copy /volume2/Aufnahme/20190210135913 - Sportschau.ts

Der ffmpeg-Prozess will ums Verrecken nicht mit sterben... Da ich selbst mit Linux, bash und Co. nicht so fit bin, werde ich mich mal hier einlesen: https://riccomini.name/kill-subprocesses-linux-bash

danielk117 commented 5 years ago

Mein .terminate() sendet SIGTERM nur an parent: https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash Wenn es ohne .sh nicht funktioniert, könnte das die Lösung sein.

Wird probiert ;-)

danielk117 commented 5 years ago

So... Änderungen am Skript:

#!/bin/sh

_term() {
  echo "caught SIGTERM signal"
  echo "$(date)"
  pkill -9 -P "$$" 2>/dev/null
}

trap _term SIGTERM

for i in "192.168.170.21" "192.168.170.45"
do
  runningfile="/volume1/@appstore/tvstreamrecord/running_$i.run"
  if [ -f $runningfile ]; then
    echo "file $runningfile exists"
  else
    echo "file $runningfile not exists"
    echo "running now" >> $runningfile
    echo "using bind adress $i"
    BIND_ADDR="$i" LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg "$@"
    rm -- "$runningfile"
    break
  fi
done

Beim Beenden über tvstreamrecord passiert nix. Alles läuft weiter. _term() wird anscheinend gar nicht aufgerufen. Rufe ich direkt in der Kommandozeile sudo pkill -9 -P $PID auf, dann wird die _term()-Funktion aufgerufen (erkenne ich an der dem Zeitstempel den mir die Funktion ausgibt).

Pavion commented 5 years ago

Wird Dein Script wieder sh aufgerufen? Damit wäre noch ein Shell drüber, wenn ich es richtig verstehe. Ansonsten bin ich mit meinem Latein am Ende...

danielk117 commented 5 years ago

Egal ob mit oder ohne sh, das Verhalten bleibt gleich. Anscheinend wird der SIGTERM-Befehl nicht korrekt gesendet oder empfangen...

danielk117 commented 5 years ago

So, ein erster Erfolg. Wie weiter oben angekündigt wollte ich mich hier https://riccomini.name/kill-subprocesses-linux-bash mal einlesen. Gesagt, getan und die dort beschriebene kill_child_processes() mittels trap angesprochen und es funktioniert.

#!/bin/sh

kill_child_processes() {
    isTopmost=$1
    curPid=$2
    childPids=`ps -o pid --no-headers --ppid ${curPid}`
    for childPid in $childPids
    do
        echo "try to kill child $childPid"
        kill_child_processes 0 $childPid
    done
    if [ $isTopmost -eq 0 ]; then
        echo "try to kill parent $curPid"
        kill -9 $curPid 2> /dev/null
    fi
    if [ -f $runningfile ]; then
        echo "try to delete $runningfile"
        rm -- "$runningfile"
    fi
}

trap "kill_child_processes 1 $$; exit 0" 0

for i in "192.168.170.21" "192.168.170.45"
do
  runningfile="/volume1/@appstore/tvstreamrecord/running_$i.run"
  if [ -f $runningfile ]; then
    echo "file $runningfile exists"
  else
    echo "file $runningfile not exists"
    echo "running now" >> $runningfile

    echo "using bind adress $i"
    BIND_ADDR="$i" LD_PRELOAD=/usr/lib/bind.so /volume1/@appstore/VideoStation/bin/ffmpeg "$@"
    rm -- "$runningfile"
    # exit
    break
  fi
done

Die kill_child_processes()-Funktion killt erst alle Kinder, dann den Hauptprozess und in meinem Fall wird noch die Running-Datei gelöscht. Sonderlich schön ist das Skript aktuell nicht, aber es funktioniert. Ich kann zwei gleichzeitge Aufnahmen über tvstreamrecord starten und beenden. Gleichzeitig sorgt das Skript dafür, dass die angegebenen IPs nur einmal für diesem Zweck verwendet werden können. Normalerweise würden sich mehrere ffmpeg-Prozesse über die gleiche IP zum FritzGerät nämlich in die Quere kommen und immer gegenseitig zum Abbruch führen.

Meine nächster Idee ist jetzt, mein eth1 um ein virtuelles Interface mit eigener IP zu erweitern und dann diese beiden Interfaces für die Aufnahmen zu nutzen. Somit wird eth0 nicht mit den Aufnahmen belastet und steht komplett für die anderen Aufgaben des NAS zur Verfügung.

Pavion commented 5 years ago

Ich will nicht behaupten, alles verstanden zu haben, aber das klingt doch cool! :)

danielk117 commented 5 years ago

1) Ja klar, wenn ich komplett fertig bin. 😉 2) Die dritte Aufnahme wird über tvstreamrecord zwar theoretisch gestartet, aber es startet nie ein ffmpeg, da mein Skript aufgrund der vorhanden Tickets normal zu Ende läuft. Danach greift aktuell deine Retry-Logik. Wenn es x-mal erfolglos probiert wurde, hört tvstreamrecord ja dann auf es zu probieren. Hier bin ich aktuell aber noch am Testen.

2019-02-11 12:55:57.017 | Something went wrong with 'Punkt 12 - Das RTL-Mittagsjournal', retry 1/20 in 10 seconds
2019-02-11 12:55:57.016 | FFMPEG record 'Punkt 12 - Das RTL-Mittagsjournal' ended

3) Theoretisch sollte auch hier die kill_child_processes() getriggert und die Tickets gelöscht werden. Kann ich aber bei Gelegenheit mal testen.

danielk117 commented 5 years ago

Also auch mit virtuellen Interfaces (über ein reales Interface) klappt das wunderbar.

Einfach Interfaces erstellen:

sudo ifconfig eth1:1 192.168.170.46
sudo ifconfig eth1:2 192.168.170.47

In meinem Skript verwende ich dann die beiden neuen IPs und das klappt wunderbar. Der gesamte Traffic läuft nun ausschließlich über eth1. 😄

Anderes Thema: Kannst du mit dieser Meldung etwas anfangen?

FFMPEG could not be started. Error: 'ascii' codec can't decode byte 0xe2 in position 275: ordinal not in range(128)

Die wird ab und zu mal geworfen und dann kommt ein erfolgreicher Retry.

Pavion commented 5 years ago

coole Sache! ^^
Ich bin gespannt, wenn das mal den Endstand erreicht hat.

Die Fehlermeldung kommt mir schon bekannt vor und sollte eig. nur kommen, wenn der Titel unzulässige Zeichen enthält, und nur unter Python 2.x

danielk117 commented 5 years ago

So, da will ich mich auch mal wieder zu Wort melden... Mein Skript ist gewachsen, und ich erklär natürlich auch warum. 😉

Probleme

Es gibt einige Themen die sich schwierig gestaltet haben:

Insgesamt alles "verbesserungswürdig"...

Was tut mein neues Skript?

  1. Exit-Händler definieren, falls Aufnahmen vorzeitig beendet werden soll.

    1. Alle Kind-Prozesse killen (z.B. ffmpeg)
    2. running-Ticket für IP löschen
    3. Den Hauptprozess killen
  2. Prüfen ob meine virtuellen Interfaces vorhanden sind.

    1. Interfaces nicht vorhanden
      1. Es erzeugt die vier virtuellen Interfaces vrt0 bis vrt3, welche allesamt über das reale Interface eth1 laufen. Alle vier haben unterschiedliche Dummy-MAC-Adressen (eigene MAC-Adressen sind notwendig, damit der Router per DCHP eigenständige IPs vergibt).
      2. Es startet für jedes Interface einen dhclient um per DHCP eine IP vom DHCP-Server zu bekommen.
    2. Interfaces vorhanden
      1. IPs auslesen.
  3. Freie IP (aus den ausgelesen vom zweiten Schritt) für Aufnahme finden anhand running-Ticket.

    1. running-Ticket für IP vorhanden, dann nächste IP prüfen.
    2. Kein running-Ticket vorhanden...
      1. Das running-Ticket schreiben
      2. Aufnahme starten
      3. Wenn Aufnahme normal beendet wird, dann wird das Ticket wieder gelöscht und der Prozess beendet.

Resultat

Ich kann also nun 4 Aufnahmen auf 2 unterschiedlich Sendern gleichzeitig durchführen. Sollte eine der 4 Aufnahme einen dritten Sender aufnahmen wollen, schlägt dieser ffmpeg-Prozess fehl und die Sendung wird nicht aufgenommen. Die anderen Prozesse laufen normal weiter. Es gilt: Wer zuerst kommt... 😄

#!/bin/sh

# exit or kill handler
kill_child_processes() {
    isTopmost=$1
    curPid=$2
    childPids=`ps -o pid --no-headers --ppid ${curPid}`

    for childPid in $childPids
    do
        echo "kill child $childPid"
        kill_child_processes 0 $childPid
    done

    if [ $isTopmost -eq 0 ]; then
        echo "kill parent $curPid"
        kill -9 $curPid 2> /dev/null
    fi

    if [ ! -z "$deletefile" ] && [ -f $deletefile ]; then
        echo "delete $deletefile"
        rm -- "$deletefile"
    fi
}
trap "kill_child_processes 1 $$; exit 0" 0

# create virtual interfaces
vrtip=$(ifconfig | awk -F"[: ]+" '/vrt/{getline;  {print $4}}')
if [ -z "$vrtip" ]; then
    echo "no vrt interfaces found"
    for i in "0" "1" "2" "3"; do
        echo "create vrt$i"
        ip link add link eth1 address 00:11:22:33:44:"$i$i" vrt"$i" type macvlan
        /usr/sbin/dhclient -4 -nw -lf /tmp/dhcpv4.leases.vrt"$i" -pf /tmp/dhcpcd-vrt"$i".pid -sf /var/run/dhclient-script vrt"$i"
    done
    sleep 10
    vrtip=$(ifconfig | awk -F"[: ]+" '/vrt/{getline;  {print $4}}')
fi
echo "vrtip: $vrtip"

# find free ip and try to record
for i in $vrtip; do
  runningfile="running_$i.run"
  if [ -f $runningfile ]; then
    echo "$runningfile exists"
  else
    echo "$runningfile not exists"
    echo "running now" >> $runningfile

    echo "using bind adress $i"
    deletefile="$runningfile"
    BIND_ADDR="$i" LD_PRELOAD=/usr/lib/bind.so /volume1/\@appstore/ffmpeg/bin/ffmpeg "$@"
    rm -- "$runningfile"
    unset deletefile

    break
  fi
done

Schlusswort

Das ist aber noch nicht die Endfassung des Skriptes. Aktuell benutze ich ein ffmpeg über eine Community-Quelle und das ist fest im Skript angegeben. Dieses Setting könnte auch als Argument an das Skript gegeben werden. Außerdem könnte man auch die Anzahl der zu erzeugenden virtuellen Interfaces als Argument verarbeiten. Es ist also immer noch Luft noch oben.

Ich hoffe man blickt einigermaßen durch.

VG Daniel

Pavion commented 5 years ago

Hi Daniel!

Also als Allererstes: WOW!

Deine Beschreibung klingt schlüssig, auch wenn ich Deinen Script nicht bis ins kleinste Detail verstehe 🤔 Einige Sachen fielen mir aber dabei ein:

Jedenfalls eine feine Sache, die Du da aufgebaut hast, bin weiterhin gespannt. Wenn Du wobei Unterstützung brauchst, melde Dich!

Grüße Pav

danielk117 commented 5 years ago

Hi Pav,

Wenn du nichts dagegen hast und offen für Erweiterungen/Neuerungen bist...

VG Daniel

Pavion commented 5 years ago

Danke für die Rückmeldung! Bevor wir jedoch weiter über Änderungen sprechen, erstmal ein paar generelle Anmerkungen:

  1. Es handelt sich um eine sechs Jahre alte Anwendung. Es war damals mein erster Versuch sowohl mit Python aber auch mit JS zu arbeiten. Leider sieht man das dem Quellcode auch heute noch an. Inzwischen bin auch ich weiter und würde heute so etwas ganz anders machen. So bin ich beruflich mit Java/JSF/JPA/PrimeFaces usw. tätig und würde heute das nehmen. Anderseits ist auch das etwas überholt und da wäre z.B. AngularJS von Interesse. Lange Rede kurzer Sinn, der aktuelle Stand ist nicht gerade erweiterungsfroh und durch mehrjährige Entwicklung recht chaotisch.
  2. Ich selbst sehe kaum bzw. gar nicht mal fern und habe auch gar kein Gerät mehr dazu. Ich leiste zwar gern Unterstützung und implementierte hin und wieder interessante Ideen, verwenden tue ich mein eigenes Tool jedoch seit langem nicht mehr.
  3. Historisch so nicht geplant, mutierte TSR zum reinen Frontend für ffmpeg. Das wäre nur halb so wild, wenn es nicht dazu geführt hätte, dass sich die meisten Fragen im Forum um ffmpeg und diverse damit verbundene Probleme handelt. Das ist oft nervig und hat eigentlich nichts mit TSR zu tun.
  4. Derzeit habe ich leider deutlich weniger Zeit und deutlich mehr Stress als damals, beschäftige mich zudem auch gern mit aktuelleren Projekten (s. meine anderen Repos).
  5. Das alles gesagt, bin ich prinzipiell für vieles offen: von der "Übergabe" in gute Hände bis hin zur gemeinsamer Neuentwicklung. Im bestehenden Konzept jedenfalls lege ich einen großen Wert auf die Nachvollziehbarkeit und Erhaltung bestehender Funktionalitäten, so dass sich auch der Bediener von vor Jahren immer wieder zurecht findet.
  6. Ich argumentiere gern, was aber nicht automatisch eine Ablehnung bedeutet, sondern eher den Wunsch, alles gut durchdacht zu haben. In diesem Sinne kurz zu Deinen letzten Ideen:
    1. Leider gibt es historisch keine klare Rückmeldung, ob eine Aufnahme gut, schlecht oder gar nicht gelaufen war. Dafür gibt es auch hier eine paar Issues. Schöner wäre es natürlich.
    2. Ja, Chart war schon sehr früh da und ist sehr ulkig aufgebaut. Schau bloß nicht in generierten HTML rein :)
    3. Mobile Ansicht hingegen kam deutlich später, was einerseits besseres Handling auf der JS Seite bedeutet, durch dann schon existierende Eigenheiten von mir nie richtig durchdacht oder realisiert worden.
    4. Und ja, realistisch gesehen, sollte alles eingestampft und neu gemacht werden...
  7. Jetzt ist es die Stelle, wo Du, sofern Du die Geduld hattest alles zu lesen, schnell abspringen darfst :) Wenn Du aber weiterhin Interesse hat, melde Dich wieder und dann können wir gern darüber diskutieren, was da der beste Weg wäre.
danielk117 commented 5 years ago

Hi!

zu 1: Verstehe ich vollkommen! War auch nicht als Vorwurf gemeint.

zu 2: Schade, aber toll dass du trotzdem dran bleibst. 👍 In meinem Fall nutze ich es nur um Sendungen für meine bessere Hälfte aufzunehmen. Durch das EPG-Chart findet man aber auch das ein oder andere was man dann doch mal aufnimmt.

zu 3: Es spricht doch auch nichts dagegen, ein Frontend für ein weit verbreitetes Tool zu betreiben. Wobei die ganze EPG-Auswertung schon über eine einfache Oberfläche hinaus geht...

zu 5: + 6.iv: Eine Neuentwicklung wäre sicherlich denkbar, aber ich wüsste nicht was an deinem Python-Backend "so schlimm ist". Das Frontend ist auch zu gebrauchen, kann aber ein Update vertragen. zu 6.i + 6.ii + 6.iii: Alles machbar. 😉

zu 7: Da ich Gefallen an deinem Tool gefunden, bleibe ich auch dran und möchte weiterhelfen.

Beste Grüße

Pavion commented 5 years ago

Huhu!

Zu 2: Hast Du eigentlich schon TV-Browser probiert? Seit ich das Tool entdeckt habe, hatte ich keine Lust mehr groß am EPG-Chart zu arbeiten...

Zu 5: Python an sich ist eine feine Sache aber hast Du Dir schonmal meine SQL-Scripte angesehen, die in *.py sind?! 🙉

Was hältst Du nun für sinnvoll? Hast Du konkrete Ideen, die ich schnell umsetzen könnte? Soll ich Dich hierfür freischalten? Oder kannst Du natürlich gern einen Fork bei Dir machen und mit PR arbeiten. Wohl gemerkt, da es sich um GPL handelt, brauchst Du für letzteres noch nicht mal meine Erlaubnis 🙂

danielk117 commented 5 years ago

Hi,

zu 2: Ne, sieht ganz nett aus, aber ich bin ein Freund von programmunabhängigen Webtools. 😉 zu 5: Ja, ich versuche gerade dein Query mit den etlichen Joins für die EPG-Chart zu verstehen... 😆

Zur letzten Frage: Was ist dir lieber bzw. einfacher für uns beide?

VG

Pavion commented 5 years ago

Jaja, die Joins... ich gebe zu, ich habe damit etwas übertrieben 🙂

Da ich immer noch nicht ganz weiß, was Du konkret vorhast, kann ich auch wenig konkretes sagen. Wenn Du eine klare Aufgabe für mich hast (z.B. in Python die und die Daten für GET bereitstellen), kann ich das - sofern es die DB hergibt - gern einprogrammieren. Erklärungen gibt's sowieso 🙂

Ansonsten, denke ich, ist es simpler, wenn Du einen Fork machst und mit PRs arbeitest, so kann man gut die Änderungen verfolgen und abgleichen. Außer Du hast andere Ideen/Erfahrungen?

danielk117 commented 5 years ago

So ich hab einen Fork gemacht. Ich denke du hast recht, über PRs läuft das am besten. Ich hab mal einen ersten PR als Test gemacht. #34

Pavion commented 5 years ago

funktioniert: https://github.com/Pavion/tvstreamrecord/commit/f92621a52c0fcdae3138b56a2d13c4b1a3286b1a 🙂

danielk117 commented 5 years ago

Hi Pavion,

hast du eine Version in höherer Auslösung von https://github.com/Pavion/tvstreamrecord/blob/master/images/apple-touch-icon.png?

VG

Pavion commented 5 years ago

Hi Daniel!

Reicht Dir 288x288?

tvstreamrecord_klein

Sonst habe ich noch die PSD-Quelle davon, die man ggf. höher skalieren könnte.

Gruß Pav

danielk117 commented 5 years ago

Das reicht fürs erste 👍

VG Daniel

Pavion commented 3 years ago

💡 Solution using Docker: https://github.com/Pavion/tvstreamrecord#running-multiple-instances-with-docker