Subprocess.Popen in threads

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
Ruffy
User
Beiträge: 34
Registriert: Dienstag 2. Oktober 2012, 11:26

Hi,

ich bin momentan dabei ein kleines tool zu basteln, welches mir alle x Minuten einen Ordner scannt und dann gegebenenfalls die Daten weiterverarbeitet.
dazu nutze ich https://github.com/senko/python-video-converter und APscheduler um den Thread zu starten.

allerdings habe ich nun das Problem, dass der video converter Popen nutzt um ffprobe/ffmpeg aufzurufen, das funktioniert auch alles wunderbar, aber sobald ich das ganze per APscheduler aufrufe bleibt der Thread 'hängen' und zwar beim ausfürhren von ffprobe, es sieht so aus als würde der Thread nicht mitbekommen dass der subprocess erfolgreich terminiert/ausgeführt wurde. Sobald der Thread 'hängt' und ich ctr + c drücke, merkt er das auch, springt dann quasi zum ende, und macht mit ffmpeg weiter...

da das ganze ja aber möglichst automatisch laufen würde wäre es natürlich klasse wenn das ganze selbstständig raus finden würde dass der subprocess beendet wurde und Daten zur Verarbeitung vorliegen.

gibt es da vielleicht ne einfache Möglichkeit auf die ich einfach nur nicht komme gerade?
Es kommt mir irgendwie so vor als würde der exit status code von ffprobe/ffmpeg einfach nicht beim thread ankommen...

bin für jede Hilfe dankbar :)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo!

Ohne Code wird es wahrscheinlich schwierig dir zu helfen. Wie sehen denn die relevanten stellen aus?
Das Leben ist wie ein Tennisball.
Ruffy
User
Beiträge: 34
Registriert: Dienstag 2. Oktober 2012, 11:26

ich weis nicht genau was davon ihr sehen wollt/müsst hier mal einige code ausschnitte....

der abschnitt in dem die convertierung aufgerufen wird...

Code: Alles auswählen

from apscheduler.scheduler import Scheduler
from converter import Converter

#start the scheduler
sched = Scheduler()
sched.start()

c = Converter()

def conversion():
    conv_string = {
        'format': 'mkv',
        'audio': {
            'codec': 'ac3',
            'bitrate': 448000,
            'channels': 6
        },
        'video': {
            'codec': 'copy'
        }
    }
    con = c.converter("/tmp/input.mkv", "/tmp/output.mkv", conv_string)
    for timecode in con:
        print timecode

sched.add_interval_job(convert, minutes=30, max_instances=2, start_date=datetime.datetime.now() + datetime.timedelta(seconds=2))
c.converter() ruft dann erst einmal ffprobe auf um die stream infos zu bekommen und im anschluss convert... hier der auszug aus der FFMpeg Klasse:

Code: Alles auswählen

@staticmethod
    def _spawn(cmds):
        logger.debug('Spawning ffmpeg with command: ' + ' '.join(cmds))
        return Popen(cmds, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE,
                     close_fds=True)

def probe(self, fname, posters_as_video=True):
 
        if not os.path.exists(fname):
            return None

        info = MediaInfo(posters_as_video)

        p = self._spawn([self.ffprobe_path,
                         '-show_format', '-show_streams', fname])
        stdout_data, _ = p.communicate()

        info.parse_ffprobe(stdout_data)

        if not info.format.format and len(info.streams) == 0:
            return None

        return info

def convert(self, infile, outfile, opts, timeout=10):
      
      if not os.path.exists(infile):
            raise FFMpegError("Input file doesn't exist: " + infile)

        cmds = [self.ffmpeg_path, '-i', infile]
        cmds.extend(opts)
        cmds.extend(['-y', outfile])

        if timeout:
            def on_sigalrm(*_):
                signal.signal(signal.SIGALRM, signal.SIG_DFL)
                raise Exception('timed out while waiting for ffmpeg')

            signal.signal(signal.SIGALRM, on_sigalrm)

        try:
            p = self._spawn(cmds)
        except OSError:
            raise FFMpegError('Error while calling ffmpeg binary')

        yielded = False
        buf = ''
        total_output = ''
        pat = re.compile(r'time=([0-9.:]+) ')
        while True:
            if timeout:
                signal.alarm(timeout)

            ret = p.stderr.read(10)

            if timeout:
                signal.alarm(0)

            if not ret:
                break

            total_output += ret
            buf += ret
            if '\r' in buf:
                line, buf = buf.split('\r', 1)

                tmp = pat.findall(line)
                if len(tmp) == 1:
                    timespec = tmp[0]
                    if ':' in timespec:
                        timecode = 0
                        for part in timespec.split(':'):
                            timecode = 60 * timecode + float(part)
                    else:
                        timecode = float(tmp[0])
                    yielded = True
                    yield timecode

        if timeout:
            signal.signal(signal.SIGALRM, signal.SIG_DFL)

        p.communicate()  # wait for process to exit

        if total_output == '':
            raise FFMpegError('Error while calling ffmpeg binary')

        cmd = ' '.join(cmds)
        if '\n' in total_output:
            line = total_output.split('\n')[-2]

            if line.startswith('Received signal'):
                # Received signal 15: terminating.
                raise FFMpegConvertError(line.split(':')[0], cmd, total_output)
            if line.startswith(infile + ': '):
                err = line[len(infile) + 2:]
                raise FFMpegConvertError('Encoding error', cmd, total_output,
                                         err)
            if line.startswith('Error while '):
                raise FFMpegConvertError('Encoding error', cmd, total_output,
                                         line)
            if not yielded:
                raise FFMpegConvertError('Unknown ffmpeg error', cmd,
                                         total_output, line)
        if p.returncode != 0:
            raise FFMpegConvertError('Exited with code %d' % p.returncode, cmd,
                                     total_output)
ich hoffe das waren/sind die relevanten code stellen, ich lass mal nen link zu bitbucket da, dort kann gegebenenfalls der komplette code eingesehen werden.
https://bitbucket.org/raphaelmutschler/pyencoder/src
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Hast du mal geschaut ob etwas in stderr von dem subprocess ankommt? Vielleicht tritt ja ein Fehler auf und das Programm terminiert gar nicht? Ansonsten könnte das natürlich an dem apscheduler liegen. Fuse lässt auch nur Threads zu, wenn man Multithreading explizit aktiviert.

Warum willst du überhaupt pollen? Reagier doch lieber auf ein auftretendes Ereignis. Mit pyinotify kann man unter Linux problemlos und ressourcenschonend Verzeichnisse auf Änderungen überwachen. In deinem Fall kannst du auf das Erstellen einer Datei mit einer bestimmten Endung reagieren und den entsprechenden Vorgang dafür starten. Das spart dir den scheduler.
Antworten