I have a... very specific problem. Really tried to find a broader question but couldn't.
I am trying to use mplayer as a subprocess to play music (on windows and also linux), and retain the ability to pass commands to it. I have accomplished this just fine in python 2.7 with subprocess.Popen
and p.stdin.write('pause\n')
.
However this doesn't seem to have survived the trip to Python 3. I have to either use 'pause\n'.encode()
or b'pause\n'
to convert to bytes
, and the mplayer process does not pause. It does seem to work however if i use p.communicate
, but I have ruled that out as a possiblity due to this question which claims it can only be called once per process.
Here's is my code:
p = subprocess.Popen('mplayer -slave -quiet "C:\\users\\me\\music\\Nickel Creek\\Nickel Creek\\07 Sweet Afton.mp3"', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
time.sleep(1)
mplayer.stdin.write(b'pause\n')
time.sleep(1)
mplayer.stdin.write(b'pause\n')
time.sleep(1)
mplayer.stdin.write(b'quit\n')
seeing as this code worked (without the b
s) in 2.7, i can only assume encoding the string as a bytes
is somehow changing the byte values so that mplayer can't understand it any more? however when i try to see exactly what bytes are sent through the pipeline it looks correct. it could also be windows pipeline acting strange. i've tried this with both cmd.exe and powershell, since i know powershell interprets the pipeline as xml. i used this code to test what comes in through the pipeline:
# test.py
if __name__ == "__main__":
x = ''
with open('test.out','w') as f:
while (len(x) == 0 or x[-1] != 'q'):
x += sys.stdin.read(1)
print(x)
f.write(x)
and
p = subprocess.Popen('python test.py', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
p.stdin.write(b'hello there\ntest2\nq\n')
seeing as this code worked (without the
b
s) in 2.7, i can only assume encoding the string as a bytes is somehow changing the byte values so that mplayer can't understand it any more?
'pause\n'
in Python 2 is exactly the same value as b'pause\n'
-- moreover you could use b'pause\n'
on Python 2 too (to communicate the intent of the code).
The difference is that bufsize=0
on Python 2 and therefore .write()
pushes the content to the subprocess immediately while .write()
on Python 3 puts it in some internal buffer instead. Add .flush()
call, to empty the buffer.
Pass universal_newlines=True
, to enable the text mode on Python 3 (then you could use 'pause\n'
instead of b'pause\n'
). You might also need it if mplayer
expects os.newline
instead of b'\n'
as the end of line.
#!/usr/bin/env python3
import time
from subprocess import Popen, PIPE
LINE_BUFFERED = 1
filename = r"C:\Users\me\...Afton.mp3"
with Popen('mplayer -slave -quiet'.split() + [filename],
stdin=PIPE, universal_newlines=True, bufsize=LINE_BUFFERED) as process:
send_command = lambda command: print(command, flush=True, file=process.stdin)
time.sleep(1)
for _ in range(2):
send_command('pause')
time.sleep(1)
send_command('quit')
Unrelated: do not use stdout=PIPE
unless you read from the pipe otherwise you may hang the child process. To discard the output, use stdout=subprocess.DEVNULL
instead. See How to hide output of subprocess in Python 2.7