I'm trying to decode mp3 to wav using ffmpeg:
import alsaaudio
import wave
from subprocess import Popen, PIPE
with open('filename.mp3', 'rb') as infile:
p=Popen(['ffmpeg', '-i', '-', '-f', 'wav', '-'], stdin=infile, stdout=PIPE)
...
Next i want redirect data from p.stdout.read() to wave.open(file, r) to use readframes(n) and other methods. But i cannot because 'file' in wave.open(file,'r') can be only name of file or an open file pointer.
...
file = wave.open(p.stdout.read(),'r')
card='default'
device=alsaaudio.PCM(card=card)
device.setchannels(file.getnchannels())
device.setrate(file.getframerate())
device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
device.setsetperiodsize(320)
data = file.readframes(320)
while data:
device.write(data)
data = file.readframes(320)
I got:
TypeError: file() argument 1 must be encoded string without NULL bytes, not str
So is it possible to handle data from p.stdout.read() by wave.open()? Making temporary .wav file isn't solution.
Sorry for my english. Thanks.
UPDATE
Thanks to PM 2Ring for hit about io.BytesIO.
However resulting code does not work.
import alsaaudio
import wave
from subprocess import Popen, PIPE
with open('sometrack.mp3', 'rb') as infile:
p=Popen(['ffmpeg', '-i', '-', '-f','wav', '-'], stdin=infile , stdout=PIPE , stderr=PIPE)
fobj = io.BytesIO(p.stdout.read())
fwave = wave.open(fobj, 'rb')
Trace:
File "./script.py", line x, in <module>
fwave = wave.open(fobj, 'rb')
File "/usr/lib/python2.7/wave.py", line x, in open
return Wave_read(f)
File "/usr/lib/python2.7/wave.py", line x, in __init__
self.initfp(f)
File "/usr/lib/python2.7/wave.py", line x, in initfp
raise Error, 'not a WAVE file'
wave.Error: not a WAVE file
From /usr/lib/python2.7/wave.py:
...
self._file = Chunk(file, bigendian = 0)
if self._file.getname() != 'RIFF':
raise Error, 'file does not start with RIFF id'
if self._file.read(4) != 'WAVE':
raise Error, 'not a WAVE file'
...
Checking has been failed due to 'bad' self._file object.
Inside /usr/lib/python2.7/chunk.py i have found a source of problem:
...
try:
self.chunksize = struct.unpack(strflag+'L', file.read(4))[0]
except struct.error:
raise EOFError
...
Because struct.unpack(strflag+'L', file.read(4))[0] returns 0. But this function works correct.
As specified here:
"5-8 bytes - File size(integer) Size of the overall file - 8 bytes, in bytes (32-bit integer). Typically, you'd fill this in after creation." That's why my script doesn't work. wave.open and other functions cannot handle my file object because self.chunksize = 0. Looks like ffmpeg cannot insert File size when using PIPE.
SOLUTION
It's simple. I've changed init function of Chunk class:
After:
...
try:
self.chunksize = struct.unpack(strflag+'L', file.read(4))[0]
except struct.error:
raise EOFError
...
Before:
...
try:
self.chunksize = struct.unpack(strflag+'L', file.read(4))[0]
currtell = file.tell()
if self.chunksize == 0:
file.seek(0)
file.read(currtell)
self.chunksize = len(file.read())-4
file.seek(0)
file.read(currtell)
except struct.error:
raise EOFError
...
Of course editing of original module is bad idia. So I've create custom forks for 2 classes Chunk and Wave_read.
Working but unstable full code you can find here.
Sorry for my awful english.
Thanks.
As you mention, the file arg to wave.open(file,'r') can only be the name of file or an open file pointer. But p.stdout.read() is the contents of the file p.stdout as a string. However, you can't just pass the p.stdout file to wave.open() because wave.open() needs a seekable file-like object, and you generally can't seek() on a pipe.
But what you can do is wrap the bytes you read from p.stdout.read() in a io.BytesIO object. The resulting object will behave like a file, and it will be seekable.
FWIW, it's not a good idea to use file as a variable name as it's the name of a built-in type in Python 2; I guess it's ok to use it Python 3, but I'd still avoid it.
Warning: untested Python 2.6+ code
fbytes = io.BytesIO(p.stdout.read())
fwave = wave.open(fbytes, 'r')
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