jb-alvarado
Anmeldungsdatum: 28. November 2012
Beiträge: 345
|
Hallo Allerseits, ich wünsch erste mal allen eine gesegnete Zeit! Ich habe bis jetzt in Python noch fast nichts gemacht, spiel aber gerade mit dem Gedanken ein Tool damit zu entwickeln. Ich könnte das ganze auch in Bash lösen, aber maches könnte in Python etwas einfacher und schneller gehen als in Bash, auch stelle ich mir die Pflege etwas leichter vor. Allerdings muss ich erst mal grundsätzlich klären, ob Python das könnte was ich gerne umsetzten wollten. Der spätere Ablauf soll sein:
XML Datei, mit Datum, pro Tag einlesen Datum, Uhrzeit und Dateipfad aus XML ermitteln prüfen ob Dateipfad existiert, wenn nicht Subprozess starten zum Mail verschicken (sollte asynchron ablaufen) das ganze obere Prozedere in einer Endlosschleife Dateipfad streamen (hier als Beispiel mit cat ...) Output des Loops (nicht des direkten Streams) zu mbuffer pipen und von da mit einem weiteren Programm abgreifen
Die ganze xml/Datum/Dateiabfrage lass ich hier mal außen vor, das wird überall gut gehen. Interessanter ist für mich die Geschichte mit dem Piping, ob das so geht. Für die XML Bearbeitung und Berechnung könnte ich mir vorstellen das Python etwas besser/schneller ist. Aber xmlstarlet unter Bash ist schon auch sehr mächtig, je schneller hier die Daten ermittelt werden können desto besser, da fehlen mir aber die Erfahrungswerte... Also was jetzt für mich interessant ist, ist folgendes: | while true; do
[...]
cat $file
done | mbuffer -L -s 265k -m 200M -p 50 | bin-out
|
Wäre so etwas in Python effizient und stabil möglich? Und wenn ja, wie würde da so ein Loop aussehen? Grüße Jonathan
|
Axel-Erfurt
Anmeldungsdatum: 18. Mai 2016
Beiträge: 1347
|
jb-alvarado schrieb: Allerdings muss ich erst mal grundsätzlich klären, ob Python das könnte was ich gerne umsetzten wollten.
Kann es würde ich sagen. Du kannst ja die meisten Befehle auch in Python ausführen lassen. Wenn Du den Output nicht in Echtzeit benötigst ist das wahrscheinlich gar nicht so schwer. z.B. mit subprocess.check_output() oder os.system(command) Ich empfehle Dir zeal , da kannst Du die python2 und python3 docs laden und sie offline nutzen. hier noch ein kleines Beispiel | #!/usr/bin/python3
# -- coding: utf-8 --
import os
output = os.system("ls /")
print (output)
|
| #!/usr/bin/python3
# -- coding: utf-8 --
import subprocess
output = subprocess.check_output("ls /", stderr=subprocess.STDOUT, shell=True).decode("utf8")
print (output)
|
|
jb-alvarado
(Themenstarter)
Anmeldungsdatum: 28. November 2012
Beiträge: 345
|
In gewisser Weiße muss das schon in Echtzeit sein. Bei meinem oberen Beispiel z.B. bekommt bin-out immer Futter vom mbuffer und bricht daher nicht ab, obwohl cat $file jedes mal neu aufgerufen wird. Sinn der Sache ist ein kontinuierlicher Datenstrom zu erzeugen, der unendlich weiter geht. Habe jetzt gelesen dass es Python Module gibt, die ein circular buffer bereit stellen, vielleicht würde das schon reichen und ich bräuchte kein mbuffer. Hätte dann aber immer noch zwei Systemaufrufe die gekoppelt werden müssen.
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4686
Wohnort: Berlin
|
@Axel-Erfurt: Die Dokumentation rät von os.system() ab und was Du da output nennst ist nicht die Ausgabe von dem ls sondern der Rückgabecode vom extern gestarteten Programm. Oder von der Shell. Und man kann nicht wirklich feststellen wo der her kommt. Das ist einer der Gründe warum man os.system() nicht mehmen sollte und auch der gleiche Grund warum man bei subprocess kein shell=True verwenden sollte, denn damit fängt man sich genau die gleichen Probleme wie bei os.system() ein. Zumal das hier ja auch gar nicht nötig ist noch eine Shell zwischen das Programm und ls zu basteln. @jb-alvarado: Die gezeigte Shell-while -Schleife könnte man in Python so ausdrücken:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | from shutil import copyfileobj
from subprocess import Popen, PIPE
# ...
mbuffer = Popen(
['mbuffer', '-L', '-s', '256k', '-m', '200M', '-p', '50'],
stdin=PIPE,
stdout=PIPE,
)
Popen(['bin-out'], stdin=mbuffer.stdout)
while True:
# ...
with open(filename, 'rb') as a_file:
copyfileobj(a_file, mbuffer.stdin)
|
|
jb-alvarado
(Themenstarter)
Anmeldungsdatum: 28. November 2012
Beiträge: 345
|
Danke Marc, das schaut schon sehr vielversprechend aus! Leider läuft bei meinem Test die while Schleife genau einmal durch, dann bleibt sie stehen. Interessanterweise wird jedoch der letzten Prozess nicht geschlossen, sondern er bleibt wie auf pause stehen. Teste das ganze gerade mit ffplay, das ist ein cli Player für Audio/Video Files: 1
2
3
4
5
6
7
8
9
10
11
12 | from shutil import copyfileobj
from subprocess import Popen, PIPE
mbuffer = Popen(
['pv', '-T', '-B', '30m'],
stdin=PIPE,
stdout=PIPE,
)
Popen(['ffplay', '-'], stdin=mbuffer.stdout)
while True:
with open('/path/testfile.mkv', 'rb') as a_file:
copyfileobj(a_file, mbuffer.stdin)
|
Habe auch open(...) in eine Variable gepackt und die noch mal explizit geschlossen, aber das half auch nichts: | while True:
ff = open('/path/testfile.mkv', 'rb')
copyfileobj(ff, mbuffer.stdin)
ff.close()
|
Edit: Ok hab's hinbekommen, ist doch nicht so schwer, denke jetzt komme ich erst mal alleine weiter ☺. Der Code schaut jetzt so aus: 1
2
3
4
5
6
7
8
9
10
11
12 | from shutil import copyfileobj
from subprocess import Popen, PIPE
mbuffer = Popen(
['pv', '-T', '-B', '30m'],
stdin=PIPE,
stdout=PIPE,
)
Popen(['ffplay', '-'], stdin=mbuffer.stdout)
while True:
ff = Popen(['ffmpeg', '-i', '/path/testfile.mkv', '-f', 'matroska', '-'], stdout=PIPE)
copyfileobj(ff.stdout, mbuffer.stdin)
|
Vielen Dank noch mal!
|
jb-alvarado
(Themenstarter)
Anmeldungsdatum: 28. November 2012
Beiträge: 345
|
Ich müsste doch noch mal was nachfragen: Ich habe jetzt diese while Schleife: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | # open a buffer for the streaming pipeline
# stdin get the files loop
# stdout pipes to ffmpeg rtmp streaming
mbuffer = subprocess.Popen(
['pv', '-q', '-B', str(buffSize)],
stdin=PIPE,
stdout=PIPE,
shell=False
)
# playout to rtmp
playout = subprocess.Popen(
[
'ffmpeg', '-hide_banner',
'-fflags', '+igndts', '-i', 'pipe:0',
'-fflags', '+genpts', '-f', 'opengl', '"test"'
],
stdin=mbuffer.stdout,
shell=False
)
# loop through files from xml playlist
# send current file to stdin from buffer
while True:
filePiper = subprocess.Popen(
[
'ffmpeg', '-v', 'error', '-hide_banner', '-nostats',
'-i', '/path/testfile.mkv',
'-s', str(width) + 'x' + str(height), '-aspect', str(aspect),
'-pix_fmt', 'yuv420p',
'-r', str(fps),
'-c:v', 'rawvideo',
'-c:a', 'pcm_s16le', '-ar', str(audSample), '-ac', '2',
'-write_index', '0',
'-f', 'avi', '-'
],
stdout=PIPE,
stderr=PIPE,
shell=False
)
if playout.pid == "":
print("yes we ended")
filePiper.kill()
mbuffer.kill()
break
copyfileobj(filePiper.stdout, mbuffer.stdin)
print("yes we are in while\n")
print('out of while')
exit()
|
Das Problem ist jetzt, dass wenn der Prozess playout stirbt, bleibt die while Schleife hängen und das Script beendet sich nicht. Auch kann ich andere Prozesse nicht schließen lassen, wie ich mit if playout.pid == "" versucht habe. Gibt es hierzu eine Lösung?
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4686
Wohnort: Berlin
|
playout.pid wird niemals die leere Zeichenkette sein. Der Wert ändert sich auch nicht auf magische Weise wenn der Prozess stirbt. Das ist immer die PID die der Prozess beim starten hatte.
Du kannst mit der poll() -Methode abfragen ob der Prozess bereits beendet ist. Die gibt entweder None oder den Rückgabecode des beendeten Prozesses zurück. Ich würde ja erst einmal freundlich terminate() verwenden und nicht gleich kill() . Das Kommandozeilenprogramm kill macht das ja auch per Voreinstellung, ist also eigentlich falsch benannt. ☺ Wieso sollte ffplay denn sterben? Bist Du sicher, dass das Problem ist? Ich vermute ja eher das ffmpeg hängt, denn wenn das Ausgaben auf seiner Fehlerausgabe macht und Du die gar nicht liest, dann blockiert der Prozess wenn der entsprechende Puffer voll ist. shell=False brauchst Du nicht angeben; das ist die Voreinstellung.
exit() brauchst Du am Ende nicht aufrufen und falls oben nicht explizit ein from sys import exit steht, ist nicht garantiert, dass die Funktion zur Verfügung steht. Das ist bei CPython zufällig so, aber das ist nicht dokumentiert. Ich verwende exit() auch nur, wenn explizit etwas anderes als 0 an den aufrufenden Prozess zurückgegeben werden soll. Ansonsten reicht das ”natürliche” Ende des Programms völlig aus.
|
jb-alvarado
(Themenstarter)
Anmeldungsdatum: 28. November 2012
Beiträge: 345
|
Marc_BlackJack_Rintsch schrieb: playout.pid wird niemals die leere Zeichenkette sein. Der Wert ändert sich auch nicht auf magische Weise wenn der Prozess stirbt. Das ist immer die PID die der Prozess beim starten hatte.
Hehe, du hast vollkommen recht - dummer Denkfehler...
Du kannst mit der poll() -Methode abfragen ob der Prozess bereits beendet ist. Die gibt entweder None oder den Rückgabecode des beendeten Prozesses zurück.
Habe das versucht abzufragen, aber sobald playout weg ist, bleibt die while Schleife hängen und es kommt nicht mehr bis zur nächsten Abfrage. Wahrscheinlich weil der buffer das verhindert.
Ich würde ja erst einmal freundlich terminate() verwenden und nicht gleich kill() . Das Kommandozeilenprogramm kill macht das ja auch per Voreinstellung, ist also eigentlich falsch benannt. ☺
Wollte erst mal auf Nummer sicher gehen, werde das aber gerne berücksichtigen ☺.
Wieso sollte ffplay denn sterben? Bist Du sicher, dass das Problem ist? Ich vermute ja eher das ffmpeg hängt, denn wenn das Ausgaben auf seiner Fehlerausgabe macht und Du die gar nicht liest, dann blockiert der Prozess wenn der entsprechende Puffer voll ist.
In dem Fall bin ich mir sicher, wobei im späteren Betrieb, wenn alles so klappt wie es soll, das sehr unwahrscheinlich ist, dass der Prozess wegbricht. Allerdings soll das Script dann als SystemD Service laufen und hier muss dann sicher gestellt werden, dass wenn ein Prozess abbricht, sich das ganze Script ordnungsgemäß beendet - damit es wieder gestartet werden kann. Daher müsste ich alle gestarteten Subprozesse überwachen und gegebenenfalls beenden.
shell=False brauchst Du nicht angeben; das ist die Voreinstellung. exit() brauchst Du am Ende nicht aufrufen und falls oben nicht explizit ein from sys import exit steht, ist nicht garantiert, dass die Funktion zur Verfügung steht. Das ist bei CPython zufällig so, aber das ist nicht dokumentiert. Ich verwende exit() auch nur, wenn explizit etwas anderes als 0 an den aufrufenden Prozess zurückgegeben werden soll. Ansonsten reicht das ”natürliche” Ende des Programms völlig aus.
Werde ich auch entfernen! Ich werde mal schauen ob ich das mit threading lösen kann. Edit: Mit threading bekomme ich es zumindest schon mal hin, dass sich das Script schließt, fehlerfrei allerdings nur mit try / except. Auch bin ich noch nicht so glücklich damit, in einem Sekundenintervall die Status der Prozesse zu prüfen. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 | # open a buffer for the streaming pipeline
# stdin get the files loop
# stdout pipes to ffmpeg rtmp streaming
mbuffer = subprocess.Popen(
['pv', '-q', '-B', str(buffSize)],
stdin=PIPE,
stdout=PIPE
)
# playout to rtmp
playout = subprocess.Popen(
[
'ffmpeg', '-hide_banner',
'-fflags', '+igndts', '-i', 'pipe:0',
'-fflags', '+genpts', '-f', 'opengl', '"test"'
],
stdin=mbuffer.stdout
)
# independent thread for clip preparation
def preProcess():
# loop through files from xml playlist
# send current file to stdin from buffer
while True:
try:
global index
clipPath, clipStart, clipDuration = getfromPlaylist(index)
index = index + 1
print('\ncorrent clip:\n' + clipPath + '\n')
filePiper = subprocess.Popen(
[
'ffmpeg', '-v', 'error', '-hide_banner', '-nostats',
'-i', clipPath,
'-s', str(width) + 'x' + str(height),
'-aspect', str(aspect),
'-pix_fmt', 'yuv420p', '-r', str(fps),
'-c:v', 'rawvideo',
'-c:a', 'pcm_s16le', '-ar', str(audSample), '-ac', '2',
'-write_index', '0',
'-f', 'avi', '-'
],
stdout=PIPE,
stderr=PIPE
)
copyfileobj(filePiper.stdout, mbuffer.stdin)
except Exception:
break
def checkProcess():
global playout, mbuffer
while True:
time.sleep(4)
if playout.poll() == 1:
mbuffer.terminate()
break
preProc = threading.Thread(name='preProcess', target=preProcess)
chkProc = threading.Thread(name='checkProcess', target=checkProcess)
preProc.start()
chkProc.start()
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4686
Wohnort: Berlin
|
@jb-alvarado: Globale Variablen, Threads, und alle Ausnahmen ”behandeln” — was soll da schon schief gehen… 😕
|
jb-alvarado
(Themenstarter)
Anmeldungsdatum: 28. November 2012
Beiträge: 345
|
Ist das ironisch Gemeint? ☺ Globale Variablen kann ich vielleicht noch etwas reduzieren, und auf Threads verzichte ich auch gerne wenn es noch andere Möglichkeiten gibt. Ausnahmen muss ich jedoch behandeln. Habe zwar vorher geschrieben es ist unwahrscheinlich, dass Prozesse wegbrechen, das war allerdings etwas voreilig. Es gibt schon Situationen wo der Fall eintreten kann. Wie gesagt, bin mit Python noch nicht ganz so vertraut, wobei mir die Sprache anfängt zu gefallen. Lässt sich schöner damit scripten als mit Shell.
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4686
Wohnort: Berlin
|
@jb-alvarado: Globale Variablen sollte man auf 0 reduzieren solange man keinen sehr guten Grund dafür nennen kann. Werte, ausser Konstanten, sollten Funktionen als Argumente betreten und nicht auf magische Weise irgendwo in der Umgebung existieren. Sonst kann man sich Funktionen auch gleich sparen. Auf Modulebene steht normalerweise nur Code der Konstanten, Klassen, und Funktionen definiert. Threads kann man verwenden, aber halt sicher und übersichtlich, also nicht mit globalen Variablen. Warum ist index überhaupt global? Und mbuffer und playout ? Mit einem lokalen index kann man beispielsweise statt der while True: -Schleife eine for index in count(): -Schleife schreiben; mit count() aus dem itertools -Modul. Noch besser währe es wenn man nicht getfromPlaylist(index) schreiben würde, was schon wieder nach globalem Zustand irgendwo hinter der Funktion aussieht, sondern wenn man die Playliste als Argument übergibt und zwar als ein Objekt das Indexzugriff implementiert, so dass man playlist[index] anstelle des Funktionsaufrufes schreiben kann. Und wenn das normal implementiert ist, braucht man den index gar nicht, denn dann könnte man auch einfach for clip_path, _, _ in playlist: schreiben. Zwei Threads und dann im Hauptthread einfach nichts mehr machen ist nicht so sinnvoll. Bei den Threads sollte man auch das daemon -Flag setzen, sonst kann man Probleme bekommen das Programm zu beenden. Ausnahmen behandeln ist ja okay, aber das tust Du ja nicht. Sinnvolle Ausnahmebehandlung macht irgendwas, und wenn es nur das protokollieren der Ausnahme samt Stacktrace ist, aber man ”verschluckt” nicht einfach alle Ausnahmen, denn nichts anderes macht Deine Behandlung ja: die Schleife wird verlassen — was auch normal passieren würde, nur das Du nicht mehr siehst warum sie verlassen wird. So bekommst Du auch nicht mit wenn da eine Aufgrund eines Programmierfehlers kommt. Die Fehlersuche kann dann sehr lustig werden. Zumal ich bei dem Code schon so eine Ahnung habe wo eine Ausnahme her kommt, und das ist ein Programmierfehler. Die Playlist wird nicht unendlich lang sein, also wird getfromPlaylist() ja irgendwann etwas anderes als ein iterierbares Objekt mit drei Elementen zurückgeben. Diesen Fall kann und sollte man behandeln ohne das man gleich alle Ausnahmen unterdrückt. Oder eben wie oben beschrieben nicht mit einem Index arbeiten, das ist in Python sowieso oft ein Zeichen das man etwas komisches macht, sondern mit einem iterierbaren Objekt. Der Name getfromPlaylist() ist übrigens auch für die vom Standard abweichende Schreibweise (get_from_playlist() ) falsch, denn in „mixed case“ müsste man das f gross schreiben (getFromPlaylist() ). Siehe auch Style Guide for Python Code. Man sollte auch sicherstellen das von allen Prozessen die man so startet auch tatsächlich der Rückgabecode abgefragt wird. Sonst erzeugt man Zombies. Gerade das ffmpeg in der Schleife ist da ein Problem. Stichwort ist hier try /finally . Das könnte dann ungefähr so aussehen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 | from shutil import copyfileobj
from subprocess import Popen, PIPE
from threading import Thread
from time import sleep
WIDTH = 320
HEIGHT = 200
ASPECT_RATIO = 4 / 3
FPS = 15
AUDIO_FREQUENCY = 22500
PLAY_BUFFER_SIZE = 8192
def play_clips(clips, out_file):
#
# TODO Class for a clip or at least a `collections.namedtuple` type.
#
for clip_path, _clip_start, _clip_duration in clips:
print('\ncurrent clip:\n{}\n'.format(clip_path))
try:
ffmpeg = Popen(
[
'ffmpeg', '-v', 'error', '-hide_banner', '-nostats',
'-i', clip_path,
'-s', '{}x{}'.format(WIDTH, HEIGHT),
'-aspect', str(ASPECT_RATIO),
'-pix_fmt', 'yuv420p', '-r', str(FPS),
'-c:v', 'rawvideo',
'-c:a', 'pcm_s16le', '-ar', str(AUDIO_FREQUENCY),
'-ac', '2',
'-write_index', '0',
'-f', 'avi', '-',
],
stdout=PIPE,
stderr=PIPE,
)
copyfileobj(ffmpeg.stdout, out_file)
finally:
ffmpeg.wait()
def check_process(process_to_watch, process_to_terminate):
while True:
sleep(4)
if process_to_watch.poll() is not None:
process_to_terminate.terminate()
break
def main():
clips = None # TODO Replace with real code.
try:
mbuffer = Popen(
['pv', '-q', '-B', str(PLAY_BUFFER_SIZE)], stdin=PIPE, stdout=PIPE
)
try:
playout = Popen(
[
'ffmpeg', '-hide_banner',
'-fflags', '+igndts', '-i', 'pipe:0',
'-fflags', '+genpts', '-f', 'opengl', '"test"'
],
stdin=mbuffer.stdout
)
play_thread = Thread(target=play_clips, args=(clips, mbuffer.stdin))
play_thread.daemon = True
play_thread.start()
check_process(playout, mbuffer)
finally:
playout.wait()
finally:
mbuffer.wait()
if __name__ == '__main__':
main()
|
|
jb-alvarado
(Themenstarter)
Anmeldungsdatum: 28. November 2012
Beiträge: 345
|
Danke für die ausführliche Erklärung und das Beispiel! Marc_BlackJack_Rintsch schrieb: @jb-alvarado: Globale Variablen sollte man auf 0 reduzieren solange man keinen sehr guten Grund dafür nennen kann. Werte, ausser Konstanten, sollten Funktionen als Argumente betreten und nicht auf magische Weise irgendwo in der Umgebung existieren. Sonst kann man sich Funktionen auch gleich sparen. Auf Modulebene steht normalerweise nur Code der Konstanten, Klassen, und Funktionen definiert.
Muss das mal mehr verinnerlichen, oft ist es eine Bequemlichkeit, oder wie in dem Fall dachte ich, ich bräuchte eine globale Funktion, weil ich nicht genau wusste wie sich mbuffer und playout verhält wenn es per Argument zur Funktion weiter gereicht wird. Threads kann man verwenden, aber halt sicher und übersichtlich, also nicht mit globalen Variablen. Warum ist index überhaupt global? Und mbuffer und playout ?
Habe gesehen, dass du in deinem Beispiel nur einen neuen Thread startest, der läuft dann parallel zum Hauptprozess - macht jetzt wo ich es sehe mehr Sinn, in dem Zusammenhang verstehe ich auch, dass globale Variablen hier ungünstig sind. index war eine ungünstig benannte Variabel, also nicht ganz das was du denkst. Die wurde einmal zu Beginn manuell auf 0 gesetzt und dann in der Playlist Funktion wurde der Wert aus der Playliste ausgelesen. Allerdings ist die Variable mittlerweile rausgeflogen weil sie so oder so nicht funktioniert hätte.
Mit einem lokalen index kann man beispielsweise statt der while True: -Schleife eine for index in count(): -Schleife schreiben; mit count() aus dem itertools -Modul. Noch besser währe es wenn man nicht getfromPlaylist(index) schreiben würde, was schon wieder nach globalem Zustand irgendwo hinter der Funktion aussieht, sondern wenn man die Playliste als Argument übergibt und zwar als ein Objekt das Indexzugriff implementiert, so dass man playlist[index] anstelle des Funktionsaufrufes schreiben kann. Und wenn das normal implementiert ist, braucht man den index gar nicht, denn dann könnte man auch einfach for clip_path, _, _ in playlist: schreiben.
Das wird das kniffligste vom ganzen Script, weil so leicht ist es leider nicht. Zu Anfang hatte ich mal angeschnitten, dass für jeden Tag eine neue Playliste eingelesen werden soll, von daher ist while True: schon das Beste, weil ja im Prinzip immer Nachschub kommt. Mit den Playlisten ist es allerdings so, dass die Inhalte mit genauer Uhrzeit gekennzeichnet sind und im Prinzip 24 Stunden durch laufen (von 6 bis 6). Das muss auch funktionieren wenn der Prozess abstürzt und dann wieder neu gestartet wird. Wenn also um 13:30 Uhr irgendwas schief läuft und das Script sich neu startet, muss es an die richtige Stelle in der Playliste springen und von dort weiter spielen. Dazu gibt es dann auch einen seek Befehl für ffmpeg. Zudem sollte jeder Clip überprüft werden, ob er wirklich existiert und wenn nicht wird ein Schwarzclip generiert und als Platzhalter eingesetzt. Zu guter Letzt soll die Playliste auch dynamisch sein. Und das ist auch der eigentliche Grund warum ich das Script machen möchte, sonst könnte ich auch einfach ffmpeg concat nehmen, gut da gäbe es noch andere Schwierigkeiten... Dynamisch heißt, dass zu jeder Zeit die aktuelle Playliste editierbar sein muss. Der Inhalt kann sich also, bis auf den aktuellen Clip stehst ändern. Die einzige Möglichkeit das umzusetzen sehe ich nur darin, dass wirklich jedes mal wen ein neuer Clip verlangt wird, die Playliste neu geladen werden muss, um den gewünschten Clip heraus zu fischen. Ist das besser, diese Funktion mit in den Thread play_clips als Unterfuktion einzubauen?
Zwei Threads und dann im Hauptthread einfach nichts mehr machen ist nicht so sinnvoll. Bei den Threads sollte man auch das daemon -Flag setzen, sonst kann man Probleme bekommen das Programm zu beenden.
Ja das macht Sinn.
Ausnahmen behandeln ist ja okay, aber das tust Du ja nicht. Sinnvolle Ausnahmebehandlung macht irgendwas, und wenn es nur das protokollieren der Ausnahme samt Stacktrace ist, aber man ”verschluckt” nicht einfach alle Ausnahmen, denn nichts anderes macht Deine Behandlung ja: die Schleife wird verlassen — was auch normal passieren würde, nur das Du nicht mehr siehst warum sie verlassen wird. So bekommst Du auch nicht mit wenn da eine Aufgrund eines Programmierfehlers kommt. Die Fehlersuche kann dann sehr lustig werden. Zumal ich bei dem Code schon so eine Ahnung habe wo eine Ausnahme her kommt, und das ist ein Programmierfehler. Die Playlist wird nicht unendlich lang sein, also wird getfromPlaylist() ja irgendwann etwas anderes als ein iterierbares Objekt mit drei Elementen zurückgeben. Diesen Fall kann und sollte man behandeln ohne das man gleich alle Ausnahmen unterdrückt. Oder eben wie oben beschrieben nicht mit einem Index arbeiten, das ist in Python sowieso oft ein Zeichen das man etwas komisches macht, sondern mit einem iterierbaren Objekt.
Das wäre wirklich ein Fehler, denke aber mit obiger Erklärung gezeigt habe, dass ich das schon berücksichtigen möchte. Vielleicht sollte man auch noch denn Fall implementieren, falls mal keine Playliste vorhanden ist. Eine andere Ausnahme ist noch, dass die Internetverbindung wegbricht und die playout function sich verabschiedet, das überwachen wir jetzt allerdings mit check_process .
Der Name getfromPlaylist() ist übrigens auch für die vom Standard abweichende Schreibweise (get_from_playlist() ) falsch, denn in „mixed case“ müsste man das f gross schreiben (getFromPlaylist() ). Siehe auch Style Guide for Python Code.
Das wusste ich nicht, werde mir das angewöhnen.
Man sollte auch sicherstellen das von allen Prozessen die man so startet auch tatsächlich der Rückgabecode abgefragt wird. Sonst erzeugt man Zombies. Gerade das ffmpeg in der Schleife ist da ein Problem. Stichwort ist hier try /finally . Das könnte dann ungefähr so aussehen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 | from shutil import copyfileobj
from subprocess import Popen, PIPE
from threading import Thread
from time import sleep
WIDTH = 320
HEIGHT = 200
ASPECT_RATIO = 4 / 3
FPS = 15
AUDIO_FREQUENCY = 22500
PLAY_BUFFER_SIZE = 8192
def play_clips(clips, out_file):
#
# TODO Class for a clip or at least a `collections.namedtuple` type.
#
for clip_path, _clip_start, _clip_duration in clips:
print('\ncurrent clip:\n{}\n'.format(clip_path))
try:
ffmpeg = Popen(
[
'ffmpeg', '-v', 'error', '-hide_banner', '-nostats',
'-i', clip_path,
'-s', '{}x{}'.format(WIDTH, HEIGHT),
'-aspect', str(ASPECT_RATIO),
'-pix_fmt', 'yuv420p', '-r', str(FPS),
'-c:v', 'rawvideo',
'-c:a', 'pcm_s16le', '-ar', str(AUDIO_FREQUENCY),
'-ac', '2',
'-write_index', '0',
'-f', 'avi', '-',
],
stdout=PIPE,
stderr=PIPE,
)
copyfileobj(ffmpeg.stdout, out_file)
finally:
ffmpeg.wait()
def check_process(process_to_watch, process_to_terminate):
while True:
sleep(4)
if process_to_watch.poll() is not None:
process_to_terminate.terminate()
break
def main():
clips = None # TODO Replace with real code.
try:
mbuffer = Popen(
['pv', '-q', '-B', str(PLAY_BUFFER_SIZE)], stdin=PIPE, stdout=PIPE
)
try:
playout = Popen(
[
'ffmpeg', '-hide_banner',
'-fflags', '+igndts', '-i', 'pipe:0',
'-fflags', '+genpts', '-f', 'opengl', '"test"'
],
stdin=mbuffer.stdout
)
play_thread = Thread(target=play_clips, args=(clips, mbuffer.stdin))
play_thread.daemon = True
play_thread.start()
check_process(playout, mbuffer)
finally:
playout.wait()
finally:
mbuffer.wait()
if __name__ == '__main__':
main()
|
Werde den Code gerne übernehmen. Verstehe nur die letzte Abfrage nicht, warum macht du das?: | if __name__ == '__main__':
main()
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4686
Wohnort: Berlin
|
So einfach wie for clip in playlist: ist es letztendlich doch, denn was immer da für jedes von der Schleife angeforderte Element passiert, kann beliebig komplex sein. Wenn das den Code in der Schleife nicht betrifft, dann kann/sollte man das da auch heraus lassen. Es kann da also durchaus für jedes Element eine Playliste immer wieder neu geladen werden. Man kann aber zu Testzwecken von dieser Funktion aber auch einfach eine Liste mit Clips übergeben. __name__ hat den Modulnamen als Zeichenkette als Wert. Ausser wenn man das Modul als Programm ausführt, dann ist der Wert 'main'. Du kannst das Modul auf diese Weise also als Programm ausführen — dann wird die main() -Funktion aufgerufen. Oder Du kannst das Modul in einem anderen Modul importieren — dann wird main() nicht aufgerufen.
Dadurch kannst Du das Modul beispielsweise in einem Python-Interpreter importieren und ”live” Einzelteile ausprobieren, zum Beispiel zur Fehlersuche. Oder man kann es für automatisierte Tests wie UnitTests importieren (lassen). Und Dokumentationswerkzeuge wie Sphinx, das auch für die Python-Dokumentation verwendet wird, können das Modul importieren und DocStrings auswerten und den Inhalt in die generierte Dokumentation einbauen.
|
jb-alvarado
(Themenstarter)
Anmeldungsdatum: 28. November 2012
Beiträge: 345
|
Marc_BlackJack_Rintsch schrieb: So einfach wie for clip in playlist: ist es letztendlich doch, denn was immer da für jedes von der Schleife angeforderte Element passiert, kann beliebig komplex sein. Wenn das den Code in der Schleife nicht betrifft, dann kann/sollte man das da auch heraus lassen. Es kann da also durchaus für jedes Element eine Playliste immer wieder neu geladen werden. Man kann aber zu Testzwecken von dieser Funktion aber auch einfach eine Liste mit Clips übergeben.
Ja klar das stimmt schon, die for Schleife bleibt, nur kann ich eben nicht die ganze Liste einfach als Objekt übergeben, weil ich die Einträge immer wieder evaluieren muss. Ansonsten mache ich das schon so.
__name__ hat den Modulnamen als Zeichenkette als Wert. Ausser wenn man das Modul als Programm ausführt, dann ist der Wert 'main'. Du kannst das Modul auf diese Weise also als Programm ausführen — dann wird die main() -Funktion aufgerufen. Oder Du kannst das Modul in einem anderen Modul importieren — dann wird main() nicht aufgerufen. Dadurch kannst Du das Modul beispielsweise in einem Python-Interpreter importieren und ”live” Einzelteile ausprobieren, zum Beispiel zur Fehlersuche. Oder man kann es für automatisierte Tests wie UnitTests importieren (lassen). Und Dokumentationswerkzeuge wie Sphinx, das auch für die Python-Dokumentation verwendet wird, können das Modul importieren und DocStrings auswerten und den Inhalt in die generierte Dokumentation einbauen.
Ah dass ist eine feine Sache, muss ich mir merken! Habe mir auch etwas das Prinzip von Klassen angeschaut > auch sehr praktisch, ob ich das momentan brauche ist wieder was anderes, aber gut zu wissen.
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4686
Wohnort: Berlin
|
@jb-alvarado: Doch Du kannst die ”ganze Liste” als Argument übergeben und die Schleife sieht dann exakt so aus: for clip in playlist: . playlist kann ein Objekt vom Typ list sein, aber eben auch jedes beliebige andere Objekt das iterierbar ist. Und in letzterem Fall hast Du ja selbst in der Hand was da bei jedem Iterationsschritt passiert um ein weiteres Element zu liefern. Du kannst da auch jedes mal eine Datei lesen und auswerten wenn Du magst. Das braucht den Code der die Clips der Reihe nach abspielt aber nicht zu interessieren wie genau die Elemente zustande kommen, der muss nur wissen das er da ein Objekt hat das Clip-Objekte liefert.
|