Cochise Ruhulessin Cochise Ruhulessin - 3 months ago 21
Python Question

Piping SoX in Python - subprocess alternative?

I use SoX in an application. The application uses it to apply various operations on audiofiles, such as trimming.

This works fine:

from subprocess import Popen, PIPE

kwargs = {'stdin': PIPE, 'stdout': PIPE, 'stderr': PIPE}

pipe = Popen(['sox','-t','mp3','-', 'test.mp3','trim','0','15'], **kwargs)
output, errors = pipe.communicate(input=open('test.mp3','rb').read())
if errors:
raise RuntimeError(errors)


This will cause problems on large files hower, since
read()
loads the complete file to memory; which is slow and may cause the pipes' buffer to overflow. A workaround exists:

from subprocess import Popen, PIPE
import tempfile
import uuid
import shutil
import os

kwargs = {'stdin': PIPE, 'stdout': PIPE, 'stderr': PIPE}
tmp = os.path.join(tempfile.gettempdir(), uuid.uuid1().hex + '.mp3')

pipe = Popen(['sox','test.mp3', tmp,'trim','0','15'], **kwargs)
output, errors = pipe.communicate()

if errors:
raise RuntimeError(errors)

shutil.copy2(tmp, 'test.mp3')
os.remove(tmp)


So the question stands as follows: Are there any alternatives to this approach, aside from writing a Python extension to the Sox C API?

Answer

A Python wrapper for SoX already exists: sox. Maybe the simplest solution is to switch to use that instead of invoking external SoX command line utilities through subprocess.

The following achieves what you want in your example using the sox package (see documentation), and should work on Linux and macOS on Python 2.7, 3.4 and 3.5 (it might also work on Windows but I haven't been able to test because I don't have access to a Windows box):

>>> import sox
>>> transformer = sox.Transformer()  # create transformer 
>>> transformer.trim(0, 15)  # trim the audio between 0 and 15 seconds 
>>> transformer.build('test.mp3', 'out.mp3')  # create the output file 

Note: this answer used to mention the no longer maintained pysox package. Thanks to @erik for the tip.

Comments