I know there are a bunch of similar questions on SO like this one or this one and maybe a couple more, but none of them seem to apply in my particular situation. My lack of understanding on how subprocess.Popen()
works doesn't help either.
What i want to achieve is: launch a subprocess (a command line radio player) that also outputs data to the terminal and can also receive input -- wait for a while -- terminate the subprocess -- exit the shell. I am running python 2.7 on OSX 10.9
Case 1. This launches the radio player (but audio only!), terminates the process, exits.
import subprocess
import time
p = subprocess.Popen(['/bin/bash', '-c', 'mplayer http://173.239.76.147:8090'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=False,
stderr=subprocess.STDOUT)
time.sleep(5)
p.kill()
Case 2. This launches the radio player, outputs information like radio name, song, bitrate, etc and also accepts input. It terminates the subprocess but it never exists the shell and the terminal becomes unusable even after using 'Ctrl-C'.
p = subprocess.Popen(['/bin/bash', '-c', 'mplayer http://173.239.76.147:8090'],
shell=False)
time.sleep(5)
p.kill()
Any ideas on how to do it? I was even thinking at the possibility of opening a slave-shell for the subprocess if there is no other choice (of course it is also something that I don't have a clue about). Thanks!
It seems like mplayer
uses the curses
library and when kill()
ing it or terminate()
ing it, for some reason, it doesn't clean the library state correctly.
To restore the terminal state you can use the reset
command.
Demo:
import subprocess, time
p = subprocess.Popen(['mplayer', 'http://173.239.76.147:8090'])
time.sleep(5)
p.terminate()
p.wait() # important!
subprocess.Popen(['reset']).wait()
print('Hello, World!')
In principle it should be possible to use stty sane
too, but it doesn't work well for me.
As Sebastian points out, there was a missing wait()
call in the above code (now added). With this wait()
call and using terminate()
the terminal doesn't get messed up (and so there shouldn't be any need for reset
).
Without the wait()
I sometimes do have problems of mixed output between the python process and mplayer
.
Also, a solution specific to mplayer
, as pointed out by Sebastian, is to send a q
to the stdin of mplayer
to quit it.
I leave the code that uses reset
because it works with any program that uses the curses
library, whether it correctly tears down the library or not, and thus it might be useful in other situations where a clean exit isn't possible.
What i want to achieve is: launch a subprocess (a command line radio player) that also outputs data to the terminal and can also receive input -- wait for a while -- terminate the subprocess -- exit the shell. I am running python 2.7 on OSX 10.9
On my system, mplayer
accepts keyboard commands e.g., q
to stop playing and quit:
#!/usr/bin/env python
import shlex
import time
from subprocess import Popen, PIPE
cmd = shlex.split("mplayer http://www.swissradio.ch/streams/6034.m3u")
p = Popen(cmd, stdin=PIPE)
time.sleep(5)
p.communicate(b'q')
It starts mplayer
tuned to public domain classical; waits 5 seconds; asks mplayer
to quit and waits for it to exit. The output is going to terminal (the same place where the python script's output goes).
I've also tried p.kill()
, p.terminate()
, p.send_signal(signal.SIGINT)
(Ctrl + C). p.kill()
creates the impression that the process hangs. Possible explanation: p.kill()
leaves some pipes open e.g., if stdout=PIPE
then your Python script might hang at p.stdout.read()
i.e., it kills the parent mplayer
process but there might be a child process that holds the pipes open. Nothing hangs with p.terminate()
, p.send_signal(signal.SIGINT)
-- mplayer
exits in an orderly manner. None of the variants I've tried require reset
.
how should I go about having both input from Python and keyboard? Do I need two different subprocesses and how to redirect the keyboard input to PIPE?
It would be much simpler just to drop stdin=PIPE
and call p.terminate(); p.wait()
instead of p.communicate(b'q')
.
If you want to keep stdin=PIPE
then the general principle is: read from sys.stdin
, write to p.stdin
until timeout happens. Given that mplayer
expects one letter commands, you need to be able to read one character at at time from sys.stdin
. The write part is easy: p.stdin.write(c)
(set bufsize=0
to avoid buffering on Python side. mplayer
doesn't buffer its stdin so you don't need to worry about it).
You don't need two different subprocesses. To implement timeout
, you could use threading.Timer(5, p.stdin.write, [b'q']).start()
or select.select
on sys.stdin
with timeout.
I guess something using the good old raw_input has nothing to do with it, or?
raw_input()
is not suitable for mplayer
because it reads the full lines but mplayer
expects one character at a time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With