Stdout aus einem Subprocess nur jede Sekunde auslesen?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
dadio
User
Beiträge: 6
Registriert: Sonntag 12. Oktober 2008, 11:53

Hola Community,

Ich versuche gerade eine Progress Anzeige für ffmpeg zu erstellen. Dabei öffne ich einen Subprocess und durchlaufe eine Schleife solange der Prozess läuft. Jetzt möchte ich die Ausgabe von ffmpeg dazu nutzen, dem User Feedback über den Fortschritt der Konvertierung zu geben.
Ich könnte den Fortschritt bei jedem Schleinfendurchgang aktualisieren, habe mir aber gedacht, es ist performanter wenn ich nur jede Sekunde einmal aktualisiere.
Das Problem mit der Lösung wie ich sie jetzt habe ist, dass das wait() scheinbar auch den Subprozess ausbremst oder zumindest die Sdtout nicht mehr in Realtime erhält. So wie es ausschaut, geht die while Schleife wirklich jede Zeile aus dem Stdout durch und holt sich nicht die aktuellste (letzte) Zeile.

Ich wäre für jeden Tipp dankbar!

Code: Alles auswählen

p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,\
universal_newlines=True,stderr=subprocess.STDOUT,stdin=None)
            
while p.poll() == None:
    time.wait(1)
    p.stdout.flush()
    out = p.stdout.readline().strip()
    print(out)
    
p.stdout.close()
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

dadio hat geschrieben:So wie es ausschaut, geht die while Schleife wirklich jede Zeile aus dem Stdout durch und holt sich nicht die aktuellste (letzte) Zeile.
Das geht ja wohl auch nicht anders. Etwas performanter wäre es aber schon, nicht jede Zeile wieder auszugeben:

Code: Alles auswählen

WAITS = 10

p = subprocess.Popen(CMD, shell=True, stdout=subprocess.PIPE,
                     universal_newlines=True, stderr=subprocess.STDOUT,
                     stdin=None)

i = WAITS
while p.poll() is None:
    p.stdout.flush()
    lines = p.stdout.read().splitlines()
    for line in lines:
        i -= 1
        if not i:
            i = WAITS
            print line.strip()
p.stdout.close()
MfG
HWK
dadio
User
Beiträge: 6
Registriert: Sonntag 12. Oktober 2008, 11:53

HWK hat geschrieben:Das geht ja wohl auch nicht anders. Etwas performanter wäre es aber schon, nicht jede Zeile wieder auszugeben
Hallo HWK,

danke für deine Antwort. Ich denke, dass Hauptproblem ist, dass ich nicht direkt an die letzte Zeile des Stdout komme. Die while Schleife, durchläuft den Sdtout Zeile für Zeile obwohl der Prozess vielleicht schon viel weiter ist. Damit stimmt die Ausgabe nicht mit dem wirklichen Stand überein.

Ich müsste also irgendwie an das Ende von Stdout springen können.

Danke!
BlackJack

@dadio: Wo kommt `time.wait()` her? Warum gehst Du den Umweg über eine Shell? Was soll `p.stderr.flush()` bewirken? (Mich wundert, dass man das überhaupt machen kann)

Ein '\' zum Fortsetzen von Quelltextzeilen braucht man nur, wenn keine Klammern mehr "offen" sind, ist in diesem Fall also nicht nötig. Die umgebrochene Zeile sollte man einrücken, damit deutlicher wird, dass das noch zur Zeile davor gehört.

``print`` ist keine Funktion.
dadio
User
Beiträge: 6
Registriert: Sonntag 12. Oktober 2008, 11:53

BlackJack hat geschrieben:@dadio: Wo kommt `time.wait()` her?
aus

Code: Alles auswählen

import time
;)
BlackJack hat geschrieben:Warum gehst Du den Umweg über eine Shell?
Um ffmpeg auszuführen und dessen Ausgabe zu erhalten. Gibt es eine andere Möglichkeit? (Ich lerne Python erst seit gestern)
BlackJack hat geschrieben:Was soll `p.stderr.flush()` bewirken?
Es soll die bisherige Ausgabe von ffmpeg löschen. (Speicher leeren)
BlackJack hat geschrieben:Ein '\' zum Fortsetzen von Quelltextzeilen braucht man nur, wenn keine Klammern mehr "offen" sind, ist in diesem Fall also nicht nötig. Die umgebrochene Zeile sollte man einrücken, damit deutlicher wird, dass das noch zur Zeile davor gehört.
Danke!
BlackJack hat geschrieben:``print`` ist keine Funktion.
Ich habe gelesen, dass print in Python 3000 nur noch als Funktion existiert, daher habe ich es schonmal so verwendet.

Hast du noch einen Tipp für die Umsetzung?
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

dadio hat geschrieben:
BlackJack hat geschrieben:Warum gehst Du den Umweg über eine Shell?
Um ffmpeg auszuführen und dessen Ausgabe zu erhalten. Gibt es eine andere Möglichkeit?
http://www.python.org/doc/2.5.2/lib/node534.html

Falls du nicht selber drauf kommen solltest: Das verwendete Popen gehört zum Subprocess-Modul.

Und deine lange Klammer liesse sich schon etwas kürzen, wenn du from-Imports verwenden würdest:

Code: Alles auswählen

from subprocess import Popen, PIPE
Zuletzt geändert von snafu am Sonntag 12. Oktober 2008, 14:49, insgesamt 1-mal geändert.
dadio
User
Beiträge: 6
Registriert: Sonntag 12. Oktober 2008, 11:53

snafu hat geschrieben:Falls du nicht selber drauf kommen solltest: Das verwendete Popen gehört zum Subprocess-Modul.
Das Problem mit communicate() ist, dass es auf das Ende des Subprozesses wartet und bis EOF liest. Das gibt es aber nicht, das der Prozess ja noch aktiv ist und weitere Ausgaben produziert.

Aber genau das verwende ich doch bereits oder?
Zuletzt geändert von dadio am Sonntag 12. Oktober 2008, 14:55, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

dadio hat geschrieben:
snafu hat geschrieben:Falls du nicht selber drauf kommen solltest: Das verwendete Popen gehört zum Subprocess-Modul.
Aber genau das verwende ich doch bereits oder?
Tust du und sollst du auch weiterhin tun. Ich wollte nur die mögliche Verwirrung vorwegnehmen, ob es sich auf der Seite vielleicht um ein anderes Popen handelt. Hat aber wohl eher das Gegenteil bewirkt und für zusätzliche Verwirrung gestiftet. ^^
BlackJack

Okay, wo kommt Dein `time` her? Denn:

Code: Alles auswählen

In [228]: import time

In [229]: time.wait
---------------------------------------------------------------------------
<type 'exceptions.AttributeError'>        Traceback (most recent call last)

/home/bj/<ipython console> in <module>()

<type 'exceptions.AttributeError'>: 'module' object has no attribute 'wait'
Falls Du `time.sleep()` meintest: Man sollte möglichst Quelltext zeigen, der "echt" ist, und nicht etwas was man sich für den Beitrag ausgedacht hat. Sonst muss man raten was den nun zum eigentlichen Problem gehört und was nur Schreibfehler sind.

Mit dem Umweg über die Shell war das ``shell=True`` gemeint. Die andere Möglichkeit ist dann offensichtlich ``shell=False`` oder diese Angabe komplett weg zu lassen.

`flush()` auf einem Dateiobjekt, aus dem nur gelesen wird, bewirkt gar nichts. Die Operation macht nur auf Dateien in die geschrieben wird Sinn.

Du kannst das Schlüsselwort ``print`` nicht als Funktion verwenden. Es sieht nur so aus, weil Du unnötige Klammern um den einzigen Ausdruck gesetzt hast und kein Leerzeichen zwischen Klammer und Schlüsselwort steht. Das ist verwirrend bis irreführend, denn obwohl ``print(obj)`` in Python 3.0 und älteren Versionen noch das gleiche bedeutet, hat ``print(a, b)`` bei beiden eine unterschiedliche Bedeutung. In Python 3.0 werden zwei Werte `a` und `b` nacheinander ausgegeben und in älteren Versionen wird *ein* Wert, nämlich ein Tupel mit den Elementen `a` und `b` ausgegeben.
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

dadio hat geschrieben:
HWK hat geschrieben:Das geht ja wohl auch nicht anders. Etwas performanter wäre es aber schon, nicht jede Zeile wieder auszugeben
Hallo HWK,

danke für deine Antwort. Ich denke, dass Hauptproblem ist, dass ich nicht direkt an die letzte Zeile des Stdout komme. Die while Schleife, durchläuft den Sdtout Zeile für Zeile obwohl der Prozess vielleicht schon viel weiter ist. Damit stimmt die Ausgabe nicht mit dem wirklichen Stand überein.

Ich müsste also irgendwie an das Ende von Stdout springen können.

Danke!
Dann vielleicht so:

Code: Alles auswählen

p = subprocess.Popen(CMD, stdout=subprocess.PIPE, universal_newlines=True,
                     stderr=subprocess.STDOUT, stdin=None)

while p.poll() is None:
    print p.stdout.read().splitlines()[-1].strip()
p.stdout.close()
Edit: Das funktioniert leider auch nicht, weil .read() bis EOF einliest.
MfG
HWK
dadio
User
Beiträge: 6
Registriert: Sonntag 12. Oktober 2008, 11:53

Danke für all die Tipps. Bis jetzt habe ich noch keine Lösung gefunden. Scheinbar muss man doch stdout in realtime verarbeiten.
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Wenn Du unter Linux programmierst, könntest Du mittels fcntl den Stream in den non-blocking-Mode schalten:

Code: Alles auswählen

fd = f.fileno()
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
Dann liest Du viele Zeichen mit read(VIELE_ZEICHEN) ein. Wenn Du weniger Zeichen zurückbekommst als angefordert, bist du am Bufferende. Da ich unter Windows arbeite, kann ich es leider nicht testen.
MfG
HWK
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

So, hier ist eine Lösung unter Windows:

Code: Alles auswählen

# File: test1.py

import msvcrt, subprocess
from win32file import ReadFile
from win32pipe import PeekNamedPipe

CMD = 'test2.py'

p = subprocess.Popen(CMD, shell=True, stdout=subprocess.PIPE,
                     universal_newlines=True, stderr=subprocess.STDOUT,
                     stdin=None)

x = msvcrt.get_osfhandle(p.stdout.fileno())
while True:
    try:
        (_, avail, _) = PeekNamedPipe(x, 0)
        if avail:
            (err_code, text) = ReadFile(x, avail, None)
            lines = text.splitlines()
            for line in lines[::-1]:
                line = line.strip()
                if line:
                    print line
                    break
    except Exception, why:
        if why[0] == 109:
            break
        raise
p.stdout.close()
shell=True ist hier notwendig, damit test2.py direkt ausgeführt werden kann.

Code: Alles auswählen

# File: test2.py

import sys
for i in range(100):
    print '**%s**' % (i + 1)
    sys.stdout.flush()
Ausgabe:

Code: Alles auswählen

>C:\Programme\Python24\pythonw -u "test1.py"
**1**
**11**
**14**
**18**
**21**
**22**
**24**
**27**
**30**
**33**
**36**
**39**
**42**
**45**
**48**
**51**
**54**
**57**
**60**
**63**
**66**
**69**
**72**
**75**
**78**
**81**
**84**
**87**
**90**
**93**
**96**
**99**
**100**
>Exit code: 0
MfG
HWK
dadio
User
Beiträge: 6
Registriert: Sonntag 12. Oktober 2008, 11:53

Hallo HWK,

vielen, vielen Dank! Das siegt doch schonmal super aus. Ich werde es gleich morgen versuchen umzusetzen. Es sollte ja unter Ubuntu nicht viel anders funktionieren.

BIG THX!
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Doch, das sieht dort völlig anders aus. Mein Code ist Windows-spezifisch. Du musst meinen Vorschlag aus dem vorherigen Post mit fcntl verwenden. Wenn Du dazu noch Fragen hast, musst Du Dich melden.
MfG
HWK
Antworten