Skip to content

Commit

Permalink
Merge 53024ea into 5984a7d
Browse files Browse the repository at this point in the history
  • Loading branch information
rabitt committed Aug 25, 2016
2 parents 5984a7d + 53024ea commit b17ebae
Show file tree
Hide file tree
Showing 8 changed files with 2,850 additions and 533 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
setup(
name='sox',

version='1.1.9',
version='1.2.0',

description='Python wrapper around SoX.',

Expand Down
216 changes: 209 additions & 7 deletions sox/combine.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@

from . import file_info
from . import core
from .core import ENCODING_VALS
from .core import enquote_filepath
from .core import is_number
from .core import sox
from .core import SoxError
from .core import SoxiError
from .core import VALID_FORMATS

from .transform import Transformer

Expand Down Expand Up @@ -71,10 +76,13 @@ def build(self, input_filepath_list, output_filepath, combine_type,
_validate_volumes(input_volumes)

input_format_list = _build_input_format_list(
input_filepath_list, input_volumes
input_filepath_list, input_volumes, self.input_format
)

_validate_file_formats(input_filepath_list, combine_type)
try:
_validate_file_formats(input_filepath_list, combine_type)
except SoxiError:
logging.warning("unable to validate file formats.")

args = []
args.extend(self.globals)
Expand All @@ -84,7 +92,7 @@ def build(self, input_filepath_list, output_filepath, combine_type,
args.extend(input_args)

args.extend(self.output_format)
args.append(output_filepath)
args.append(enquote_filepath(output_filepath))
args.extend(self.effects)

status, out, err = sox(args)
Expand All @@ -104,6 +112,165 @@ def build(self, input_filepath_list, output_filepath, combine_type,
logging.info("[SoX] {}".format(out))
return True

def set_input_format(self, file_type=None, rate=None, bits=None,
channels=None, encoding=None, ignore_length=None):
'''Sets input file format arguments. This is primarily useful when
dealing with audio files without a file extension. Overwrites any
previously set input file arguments.
If this function is not explicity called the input format is inferred
from the file extension or the file's header.
Parameters
----------
file_type : list of str or None, default=None
The file type of the input audio file. Should be the same as what
the file extension would be, for ex. 'mp3' or 'wav'.
rate : list of float or None, default=None
The sample rate of the input audio file. If None the sample rate
is inferred.
bits : list of int or None, default=None
The number of bits per sample. If None, the number of bits per
sample is inferred.
channels : list of int or None, default=None
The number of channels in the audio file. If None the number of
channels is inferred.
encoding : list of str or None, default=None
The audio encoding type. Sometimes needed with file-types that
support more than one encoding type. One of:
* signed-integer : PCM data stored as signed (‘two’s
complement’) integers. Commonly used with a 16 or 24−bit
encoding size. A value of 0 represents minimum signal
power.
* unsigned-integer : PCM data stored as unsigned integers.
Commonly used with an 8-bit encoding size. A value of 0
represents maximum signal power.
* floating-point : PCM data stored as IEEE 753 single precision
(32-bit) or double precision (64-bit) floating-point
(‘real’) numbers. A value of 0 represents minimum signal
power.
* a-law : International telephony standard for logarithmic
encoding to 8 bits per sample. It has a precision
equivalent to roughly 13-bit PCM and is sometimes encoded
with reversed bit-ordering.
* u-law : North American telephony standard for logarithmic
encoding to 8 bits per sample. A.k.a. μ-law. It has a
precision equivalent to roughly 14-bit PCM and is sometimes
encoded with reversed bit-ordering.
* oki-adpcm : OKI (a.k.a. VOX, Dialogic, or Intel) 4-bit ADPCM;
it has a precision equivalent to roughly 12-bit PCM. ADPCM
is a form of audio compression that has a good compromise
between audio quality and encoding/decoding speed.
* ima-adpcm : IMA (a.k.a. DVI) 4-bit ADPCM; it has a precision
equivalent to roughly 13-bit PCM.
* ms-adpcm : Microsoft 4-bit ADPCM; it has a precision
equivalent to roughly 14-bit PCM.
* gsm-full-rate : GSM is currently used for the vast majority
of the world’s digital wireless telephone calls. It
utilises several audio formats with different bit-rates and
associated speech quality. SoX has support for GSM’s
original 13kbps ‘Full Rate’ audio format. It is usually
CPU-intensive to work with GSM audio.
ignore_length : list of bool or None, default=None
If True, overrides an (incorrect) audio length given in an audio
file’s header. If this option is given then SoX will keep reading
audio until it reaches the end of the input file.
'''
if file_type is not None and not isinstance(file_type, list):
raise ValueError("file_type must be a list or None.")

if file_type is not None:
if not all([f in VALID_FORMATS for f in file_type]):
raise ValueError(
'file_type elements '
'must be one of {}'.format(VALID_FORMATS)
)
else:
file_type = []

if rate is not None and not isinstance(rate, list):
raise ValueError("rate must be a list or None.")

if rate is not None:
if not all([is_number(r) and r > 0 for r in rate]):
raise ValueError('rate elements must be positive floats.')
else:
rate = []

if bits is not None and not isinstance(bits, list):
raise ValueError("bits must be a list or None.")

if bits is not None:
if not all([isinstance(b, int) and b > 0 for b in bits]):
raise ValueError('bit elements must be positive ints.')
else:
bits = []

if channels is not None and not isinstance(channels, list):
raise ValueError("channels must be a list or None.")

if channels is not None:
if not all([isinstance(c, int) and c > 0 for c in channels]):
raise ValueError('channel elements must be positive ints.')
else:
channels = []

if encoding is not None and not isinstance(encoding, list):
raise ValueError("encoding must be a list or None.")

if encoding is not None:
if not all([e in ENCODING_VALS for e in encoding]):
raise ValueError(
'elements of encoding must '
'be one of {}'.format(ENCODING_VALS)
)
else:
encoding = []

if ignore_length is not None and not isinstance(ignore_length, list):
raise ValueError("ignore_length must be a list or None.")

if ignore_length is not None:
if not all([isinstance(l, bool) for l in ignore_length]):
raise ValueError("ignore_length elements must be booleans.")
else:
ignore_length = []

max_input_arg_len = max([
len(file_type), len(rate), len(bits), len(channels),
len(encoding), len(ignore_length)
])

input_format = []
for _ in range(max_input_arg_len):
input_format.append([])

for i, f in enumerate(file_type):
input_format[i].extend(['-t', '{}'.format(f)])

for i, r in enumerate(rate):
input_format[i].extend(['-r', '{}'.format(r)])

for i, b in enumerate(bits):
input_format[i].extend(['-b', '{}'.format(b)])

for i, c in enumerate(channels):
input_format[i].extend(['-c', '{}'.format(c)])

for i, e in enumerate(encoding):
input_format[i].extend(['-e', '{}'.format(e)])

for i, l in enumerate(ignore_length):
if l is True:
input_format[i].append('--ignore-length')

self.input_format = input_format
return self

def splice(self):
raise NotImplementedError


def _validate_file_formats(input_filepath_list, combine_type):
'''Validate that combine method can be performed with given files.
Expand Down Expand Up @@ -144,19 +311,30 @@ def _validate_num_channels(input_filepath_list, combine_type):
)


def _build_input_format_list(input_filepath_list, input_volumes):
def _build_input_format_list(input_filepath_list, input_volumes=None,
input_format=None):
'''Set input formats given input_volumes.
Parameters
----------
input_filepath_list : list of str
List of input files
input_volumes : list of float, default=None
List of volumes to be applied upon combining input files. Volumes
are applied to the input files in order.
If None, input files will be combined at their original volumes.
input_format : list of lists, default=None
List of input formats to be applied to each input file. Formatting
arguments are applied to the input files in order.
If None, the input formats will be inferred from the file header.
'''
n_inputs = len(input_filepath_list)
input_format_list = []
for _ in range(n_inputs):
input_format_list.append([])

# Adjust length of input_volumes list
if input_volumes is None:
vols = [1] * n_inputs
else:
Expand All @@ -178,8 +356,32 @@ def _build_input_format_list(input_filepath_list, input_volumes):
else:
vols = [v for v in input_volumes]

for vol in vols:
input_format_list.append(['-v', '{}'.format(vol)])
# Adjust length of input_format list
if input_format is None:
fmts = [[] for _ in range(n_inputs)]
else:
n_fmts = len(input_format)
if n_fmts < n_inputs:
logging.warning(
'Input formats were only specified for %s out of %s files.'
'The last %s files will remain unformatted.',
n_fmts, n_inputs, n_inputs - n_fmts
)
fmts = [f for f in input_format]
fmts.extend([[] for _ in range(n_inputs - n_fmts)])
elif n_fmts > n_inputs:
logging.warning(
'%s Input formats were specified but only %s input files exist'
'. The last %s formats will be ignored.',
n_fmts, n_inputs, n_fmts - n_inputs
)
fmts = input_format[:n_inputs]
else:
fmts = [f for f in input_format]

for i, (vol, fmt) in enumerate(zip(vols, fmts)):
input_format_list[i].extend(['-v', '{}'.format(vol)])
input_format_list[i].extend(fmt)

return input_format_list

Expand All @@ -197,7 +399,7 @@ def _build_input_args(input_filepath_list, input_format_list):
zipped = zip(input_filepath_list, input_format_list)
for input_file, input_fmt in zipped:
input_args.extend(input_fmt)
input_args.append(input_file)
input_args.append(enquote_filepath(input_file))

return input_args

Expand Down
12 changes: 10 additions & 2 deletions sox/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

SOXI_ARGS = ['b', 'c', 'a', 'D', 'e', 't', 's', 'r']

ENCODING_VALS = [
'signed-integer', 'unsigned-integer', 'floating-point', 'a-law', 'u-law',
'oki-adpcm', 'ima-adpcm', 'ms-adpcm', 'gsm-full-rate'
]


def enquote_filepath(fpath):
"""Wrap a filepath in double-quotes to protect difficult characters.
Expand Down Expand Up @@ -37,11 +42,14 @@ def sox(args):
args[0] = "sox"

try:
logging.info("Executing: %s", " ".join(args))
command = ' '.join(args)
logging.info("Executing: %s", command)

process_handle = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=True
)

out, err = process_handle.communicate()
out = out.decode("utf-8")
err = err.decode("utf-8")
Expand Down
4 changes: 2 additions & 2 deletions sox/file_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def validate_input_file(input_filepath):
ext = file_extension(input_filepath)
if ext not in VALID_FORMATS:
logging.info("Valid formats: %s", " ".join(VALID_FORMATS))
raise SoxError(
logging.warning(
"This install of SoX cannot process .{} files.".format(ext)
)

Expand Down Expand Up @@ -270,7 +270,7 @@ def validate_output_file(output_filepath):
ext = file_extension(output_filepath)
if ext not in VALID_FORMATS:
logging.info("Valid formats: %s", " ".join(VALID_FORMATS))
raise SoxError(
logging.warning(
"This install of SoX cannot process .{} files.".format(ext)
)

Expand Down

0 comments on commit b17ebae

Please sign in to comment.