質問

I asked a question related to this several weeks ago on here: Python, mpg123 and subprocess not properly using stdin.write or communicate

Thanks to help from there I was able to do what I needed at the time. (Didn't call q, but terminated the subprocess to stop it).## Heading ## Now though I seem to be in another bit of a mess.

    from subprocess import Popen, PIPE, STDOUT
    p = Popen(["mpg123", "-C", "test.mp3"], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
    #wait a few seconds to enter this, "q" without a newline is how the controls for the player work to quit out if it were ran like "mpg123 -C test.mp3" on the command line
    p.communicate(input='q')[0]

much like before, I need this to be able to quit out of mpg123 like it would be with it's standard controls (like press 'q' to quit, or '-' to turn volume down, '+' to turn volume up, etc), now I use the code above, which should theoretically work, and it works with similar programs. Does anyone know of a way I can use the controls built into mpg123 (the one accessible by using "mpg123 -C whatever.mp3") using a subprocess? terminate isn't enough anymore as I will need the controls ^_^

EDIT: Many thanks to abarnert for the amazing answer =) ok, so the new code is simply a slightly modified version of abarnert's answer, however mpg123 doesn't seem to be accepting the commands

    import os
    import pty
    import sys
    import time

    pid, fd = os.forkpty()
    if pid:
        time.sleep(5)
        os.write(fd, 'b') #this should've restarted the file
        time.sleep(5)
        os.write(fd, 'q') #unfortunately doesn't quit here =(
        time.sleep(5) # quits after this is finished executing
    else:
        os.spawnl(os.P_WAIT, '/usr/bin/mpg123', '-C', 'TEST file.mp3')
役に立ちましたか?

解決

If you really need the controls, you can't just use Popen.

mpg123 only enables terminal control if its stdin is a tty, not if it's a file or pipe. That's why you get this line in the banner:

Terminal control enabled, press 'h' for listing of keys and functions.

And the whole point of Popen (and subprocess, and the POSIX APIs it's built on) is pipes.

So, what can you do about it?


On linux, you can use the pty module. It may also work on other *nix platforms, but it may not—even if it gets built and included in your stdlib. As the docs say:

Because pseudo-terminal handling is highly platform dependent, there is code to do it only for Linux. (The Linux code is supposed to work on other platforms, but hasn’t been tested yet.)

It definitely runs on *BSD platforms on 2.7 and 3.3, and the example in the docs seem to work on both Mac OS X and FreeBSD… but that's as far as I've checked.


Meanwhile, most POSIX platforms will at least have os.forkpty, and that's not much harder, so here's a trivial program that plays the first 5 seconds of a song passed as its first arg:

import os
import pty
import sys
import time

pid, fd = os.forkpty()
if pid:
    time.sleep(5)
    os.write(fd, 'q')
else:
    os.spawnl(os.P_WAIT, # mode
              '/usr/local/bin/mpg123', # path
              '/usr/local/bin/mpg123', '-C', sys.argv[1]) # args

Note that I used os.spawnl above. This is probably not what you want in a real program; it's for pedagogic purposes, to encourage you to read the docs (and the corresponding manpages) and understand this family of functions.

As the docs explain, this does not use the PATH environment variable, so you need to specify the full path to the program. You can just use spawnlp instead of spawnl to fix this.

Also, spawn may (in fact, always does, although the docs aren't entirely clear) do another fork to execute the child. This really isn't necessary, but spawn does things that you would need to do manually if you just called exec. If you know what you're doing, you may well want to use execl (or execlp) instead of spawnl.

You can even use most of the functionality in subprocess as long as you're careful (do not create any pipes, and remember that you'll end up doing two forks, so make sure to set up the parent/child relationship properly).

Also notice that you need to pass the path to mpg123 twice—once as the path, and then once as the child program's argv[0]. You could also just pass mpg123 the second time. Or, ideally, look at what ps says when you run it from the shell, and pass that. At any rate, you have to pass something as the argv[0]; otherwise, -C ends up being the argv[0], which means mpg123 won't think you gave it a -C flag to enable control keys, but rather than you renamed it to -C and ran it with no flags…

Anyway, you really do need to read the docs to understand what each of these functions does, instead of just treating it like magic code that you don't understand. So, I intentionally used the simplest possible solution to encourage that.


On Windows, there is no such thing as a pty, and no way to do this at all with the facilities built in to Python. You will need to use one of the various third-party libraries for controlling a cmd.exe console (aka DOS prompt) instead.

他のヒント

Based on abarnert's idea, we can open a pseudo-terminal and pass it to subprocess.

import os
import pty
import subprocess
import time

master, slave = os.openpty()

p = subprocess.Popen(['mpg123', '-C', 'music.mp3'], stdin=master)
time.sleep(3)
os.write(slave, 's')
time.sleep(3)
os.write(slave, 's')
time.sleep(6)
os.write(slave, 'q')
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top