Backstory For Readers
Established in chat and comments:
cat temp.mp3 | omxplayer -o local --vol -500 /dev/stdin
causes a segfault.
omxplayer -o local --vol -500 /dev/fd/3 3< <(cat temp.mp3)
works correctly.
Thus, we can pass a MP3's data in... but not on stdin (which omxplayer
uses for controls: pausing, early exiting, etc).
Approach 1: Using A Shell For File Descriptor Wrangling
This is equivalent to "Approach 3", but instead of using very new and modern Python functionality to do the FD wrangling in-process, it launches a copy of /bin/sh
to do the work (and consequently will work with much older Python releases).
play_from_stdin_sh = '''
exec 3<&0 # copy stdin to FD 3
exec </dev/tty || exec </dev/null # make stdin now be /dev/tty or /dev/null
exec omxplayer -o local --vol -500 /dev/fd/3 # play data from FD 3
'''
p = subprocess.Popen(['sh', '-c', play_from_stdin_sh], stdin=subprocess.POPEN)
p.communicate(song) # passes data in "song" as stdin to the copy of sh
Because omxplayer
expects to use stdin to get instructions from its user, we need to use a different file descriptor for passing in its contents. Thus, while we have the Python interpreter pass content on stdin, we then have a shell copy stdin to FD 3 and replace the original stdin with a handle or either /dev/tty
or /dev/null
before invoking omxplayer
.
Approach 2: Using A Named Pipe
There's a little bit of a question as to whether this is cheating on the "no writing to disk" constraint. It doesn't write any of the MP3 data to disk, but it does create a filesystem object that both processes can open as a way to connect to each other, even though the data written to that object flows directly between the processes, without being written to disk.
import tempfile, os, os.path, shutil, subprocess
fifo_dir = None
try:
fifo_dir = tempfile.mkdtemp('mp3-fifodir')
fifo_name = os.path.join(fifo_dir, 'fifo.mp3')
os.mkfifo(fifo_name)
# now, we start omxplayer, and tell it to read from the FIFO
# as long as it opens it in read mode, it should just hang until something opens
# ...the write side of the FIFO, writes content to it, and then closes it.
p = subprocess.Popen(['omxplayer', '-o', 'local', '--vol', '-500', fifo_name])
# this doesn't actually write content to a file on disk! instead, it's written directly
# ...to the omxplayer process's handle on the other side of the FIFO.
fifo_fd = open(fifo_name, 'w')
fifo_fd.write(song)
fifo_fd.close()
p.wait()
finally:
shutil.rmtree(fifo_dir)
Approach 3: Using A preexec_fn
In Python
We can implement the file descriptor wrangling that Approach 1 used a shell for in native Python using the Popen
object's preexec_fn
argument. Consider:
import os, subprocess
def move_stdin():
os.dup2(0, 3) # copy our stdin -- FD 0 -- to FD 3
try:
newstdin = open('/dev/tty', 'r') # open /dev/tty...
os.dup2(newstdin.fileno(), 0) # ...and attach it to FD 0.
except IOError:
newstdin = open('/dev/null', 'r') # Couldn't do that? Open /dev/null...
os.dup2(newstdin.fileno(), 0) # ...and attach it to FD 0.
p = subprocess.Popen(['omxplayer', '-o', 'local', '--vol', '-500', '/dev/fd/3'],
stdin=subprocess.PIPE, preexec_fn=move_stdin, pass_fds=[0,1,2,3])
p.communicate(song)