def watermark(im, mark, position, opacity=1):
    """Adds a watermark to an image."""
    if opacity < 1:
        mark = reduce_opacity(mark, opacity)
    if im.mode != 'RGBA':
        im = im.convert('RGBA')
    # create a transparent layer the size of the image and draw the
    # watermark in that layer.
    layer = Image.new('RGBA', im.size, (0, 0, 0, 0))
    if position == 'tile':
        for y in range(0, im.size[1], mark.size[1]):
            for x in range(0, im.size[0], mark.size[0]):
                layer.paste(mark, (x, y))
    elif position == 'scale':
        # scale, but preserve the aspect ratio
        ratio = min(
            float(im.size[0]) / mark.size[0], float(im.size[1]) / mark.size[1])
        w = int(mark.size[0] * ratio)
        h = int(mark.size[1] * ratio)
        mark = mark.resize((w, h))
        layer.paste(mark, (int((im.size[0] - w) / 2),
                           int((im.size[1] - h) / 2)))
    else:
        layer.paste(mark, position)
    # composite the watermark with the layer
    return Image.composite(layer, im, layer)

def watermark ( im , mark , position , opacity = 1 ) : if opacity < 1 : mark = reduce_opacity ( mark , opacity ) if im . mode != 'RGBA' : im = im . convert ( 'RGBA' ) layer = Image . new ( 'RGBA' , im . size , ( 0 , 0 , 0 , 0 ) ) if position == 'tile' : for y in range ( 0 , im . size [ 1 ] , mark . size [ 1 ] ) : for x in range ( 0 , im . size [ 0 ] , mark . size [ 0 ] ) : layer . paste ( mark , ( x , y ) ) elif position == 'scale' : ratio = min ( float ( im . size [ 0 ] ) / mark . size [ 0 ] , float ( im . size [ 1 ] ) / mark . size [ 1 ] ) w = int ( mark . size [ 0 ] * ratio ) h = int ( mark . size [ 1 ] * ratio ) mark = mark . resize ( ( w , h ) ) layer . paste ( mark , ( int ( ( im . size [ 0 ] - w ) / 2 ) , int ( ( im . size [ 1 ] - h ) / 2 ) ) ) else : layer . paste ( mark , position ) return Image . composite ( layer , im , layer )


def check_subprocess(cmd, source, outname):
    """Run the command to resize the video and remove the output file if the
    processing fails.

    """
    logger = logging.getLogger(__name__)
    try:
        res = subprocess.run(cmd, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
    except KeyboardInterrupt:
        logger.debug('Process terminated, removing file %s', outname)
        if os.path.isfile(outname):
            os.remove(outname)
        raise

    if res.returncode:
        logger.debug('STDOUT:\n %s', res.stdout.decode('utf8'))
        logger.debug('STDERR:\n %s', res.stderr.decode('utf8'))
        if os.path.isfile(outname):
            logger.debug('Removing file %s', outname)
            os.remove(outname)
        raise SubprocessException('Failed to process ' + source)

def
check_subprocess
(
cmd
,
source
,
outname
)
:
logger
=
logging
.
getLogger
(
__name__
)
try
:
res
=
subprocess
.
run
(
cmd
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
)
except
KeyboardInterrupt
:
logger
.
debug
(
'Process terminated, removing file %s'
,
outname
)
if
os
.
path
.
isfile
(
outname
)
:
os
.
remove
(
outname
)
raise
if
res
.
returncode
:
logger
.
debug
(
'STDOUT:\n %s'
,
res
.
stdout
.
decode
(
'utf8'
)
)
logger
.
debug
(
'STDERR:\n %s'
,
res
.
stderr
.
decode
(
'utf8'
)
)
if
os
.
path
.
isfile
(
outname
)
:
logger
.
debug
(
'Removing file %s'
,
outname
)
os
.
remove
(
outname
)
raise
SubprocessException
(
'Failed to process '
+
source
)

Run the command to resize the video and remove the output file if the processing fails .


def video_size(source, converter='ffmpeg'):
    """Returns the dimensions of the video."""

    res = subprocess.run([converter, '-i', source], stderr=subprocess.PIPE)
    stderr = res.stderr.decode('utf8')
    pattern = re.compile(r'Stream.*Video.* ([0-9]+)x([0-9]+)')
    match = pattern.search(stderr)
    rot_pattern = re.compile(r'rotate\s*:\s*-?(90|270)')
    rot_match = rot_pattern.search(stderr)

    if match:
        x, y = int(match.groups()[0]), int(match.groups()[1])
    else:
        x = y = 0
    if rot_match:
        x, y = y, x
    return x, y

def
video_size
(
source
,
converter
=
'ffmpeg'
)
:
res
=
subprocess
.
run
(
[
converter
,
'-i'
,
source
]
,
stderr
=
subprocess
.
PIPE
)
stderr
=
res
.
stderr
.
decode
(
'utf8'
)
pattern
=
re
.
compile
(
r'Stream.*Video.* ([0-9]+)x([0-9]+)'
)
match
=
pattern
.
search
(
stderr
)
rot_pattern
=
re
.
compile
(
r'rotate\s*:\s*-?(90|270)'
)
rot_match
=
rot_pattern
.
search
(
stderr
)
if
match
:
x
,
y
=
int
(
match
.
groups
(
)
[
0
]
)
,
int
(
match
.
groups
(
)
[
1
]
)
else
:
x
=
y
=
0
if
rot_match
:
x
,
y
=
y
,
x
return
x
,
y


def generate_video(source, outname, settings, options=None):
    """Video processor.

    :param source: path to a video
    :param outname: path to the generated video
    :param settings: settings dict
    :param options: array of options passed to ffmpeg

    """
    logger = logging.getLogger(__name__)

    # Don't transcode if source is in the required format and
    # has fitting datedimensions, copy instead.
    converter = settings['video_converter']
    w_src, h_src = video_size(source, converter=converter)
    w_dst, h_dst = settings['video_size']
    logger.debug('Video size: %i, %i -> %i, %i', w_src, h_src, w_dst, h_dst)

    base, src_ext = splitext(source)
    base, dst_ext = splitext(outname)
    if dst_ext == src_ext and w_src <= w_dst and h_src <= h_dst:
        logger.debug('Video is smaller than the max size, copying it instead')
        shutil.copy(source, outname)
        return

    # http://stackoverflow.com/questions/8218363/maintaining-ffmpeg-aspect-ratio
    # + I made a drawing on paper to figure this out
    if h_dst * w_src < h_src * w_dst:
        # biggest fitting dimension is height
        resize_opt = ['-vf', "scale=trunc(oh*a/2)*2:%i" % h_dst]
    else:
        # biggest fitting dimension is width
        resize_opt = ['-vf', "scale=%i:trunc(ow/a/2)*2" % w_dst]

    # do not resize if input dimensions are smaller than output dimensions
    if w_src <= w_dst and h_src <= h_dst:
        resize_opt = []

    # Encoding options improved, thanks to
    # http://ffmpeg.org/trac/ffmpeg/wiki/vpxEncodingGuide
    cmd = [converter, '-i', source, '-y']  # -y to overwrite output files
    if options is not None:
        cmd += options
    cmd += resize_opt + [outname]

    logger.debug('Processing video: %s', ' '.join(cmd))
    check_subprocess(cmd, source, outname)


def generate_thumbnail(source, outname, box, delay, fit=True, options=None,
                       converter='ffmpeg'):
    """Create a thumbnail image for the video source, based on ffmpeg."""

    logger = logging.getLogger(__name__)
    tmpfile = outname + ".tmp.jpg"

    # dump an image of the video
    cmd = [converter, '-i', source, '-an', '-r', '1',
           '-ss', delay, '-vframes', '1', '-y', tmpfile]
    logger.debug('Create thumbnail for video: %s', ' '.join(cmd))
    check_subprocess(cmd, source, outname)

    # use the generate_thumbnail function from sigal.image
    image.generate_thumbnail(tmpfile, outname, box, fit=fit, options=options)
    # remove the image
    os.unlink(tmpfile)

def
generate_thumbnail
(
source
,
outname
,
box
,
delay
,
fit
=
True
,
options
=
,
converter
=
'ffmpeg'
)
:
logger
=
logging
.
getLogger
(
__name__
)
tmpfile
=
outname
+
".tmp.jpg"
cmd
=
[
converter
,
'-i'
,
source
,
'-an'
,
'-r'
,
'1'
,
'-ss'
,
delay
,
'-vframes'
,
'1'
,
'-y'
,
tmpfile
]
logger
.
debug
(
'Create thumbnail for video: %s'
,
' '
.
join
(
cmd
)
)
check_subprocess
(
cmd
,
source
,
outname
)
image
.
generate_thumbnail
(
tmpfile
,
outname
,
box
,
fit
=
fit
,
options
=
options
)
os
.
unlink
(
tmpfile
)

Create a thumbnail image for the video source based on ffmpeg .


def generate_context(self, album):
        """Generate the context dict for the given path."""

        from . import __url__ as sigal_link
        self.logger.info("Output album : %r", album)
        return {
            'album': album,
            'index_title': self.index_title,
            'settings': self.settings,
            'sigal_link': sigal_link,
            'theme': {'name': os.path.basename(self.theme),
                      'url': url_from_path(os.path.relpath(self.theme_path,
                                                           album.dst_path))},
        }

def
generate_context
(
self
,
album
)
:
from
.
import
__url__
as
sigal_link
self
.
logger
.
info
(
"Output album : %r"
,
album
)
return
{
'album'
:
album
,
'index_title'
:
self
.
index_title
,
'settings'
:
self
.
settings
,
'sigal_link'
:
sigal_link
,
'theme'
:
{
'name'
:
os
.
path
.
basename
(
self
.
theme
)
,
'url'
:
url_from_path
(
os
.
path
.
relpath
(
self
.
theme_path
,
album
.
dst_path
)
)
}
,
}

Generate the context dict for the given path .


def write(self, album):
        """Generate the HTML page and save it."""

        page = self.template.render(**self.generate_context(album))
        output_file = os.path.join(album.dst_path, album.output_file)

        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(page)

def write ( self , album ) : page = self . template . render ( ** self . generate_context ( album ) ) output_file = os . path . join ( album . dst_path , album . output_file ) with open ( output_file , 'w' , encoding = 'utf-8' ) as f : f . write ( page )


def get_thumb(settings, filename):
    """Return the path to the thumb.

    examples:
    >>> default_settings = create_settings()
    >>> get_thumb(default_settings, "bar/foo.jpg")
    "bar/thumbnails/foo.jpg"
    >>> get_thumb(default_settings, "bar/foo.png")
    "bar/thumbnails/foo.png"

    for videos, it returns a jpg file:
    >>> get_thumb(default_settings, "bar/foo.webm")
    "bar/thumbnails/foo.jpg"
    """

    path, filen = os.path.split(filename)
    name, ext = os.path.splitext(filen)

    if ext.lower() in settings['video_extensions']:
        ext = '.jpg'
    return join(path, settings['thumb_dir'], settings['thumb_prefix'] +
                name + settings['thumb_suffix'] + ext)

def get_thumb ( settings , filename ) : path , filen = os . path . split ( filename ) name , ext = os . path . splitext ( filen ) if ext . lower ( ) in settings [ 'video_extensions' ] : ext = '.jpg' return join ( path , settings [ 'thumb_dir' ] , settings [ 'thumb_prefix' ] + name + settings [ 'thumb_suffix' ] + ext )


def read_settings(filename=None):
    """Read settings from a config file in the source_dir root."""

    logger = logging.getLogger(__name__)
    logger.info("Reading settings ...")
    settings = _DEFAULT_CONFIG.copy()

    if filename:
        logger.debug("Settings file: %s", filename)
        settings_path = os.path.dirname(filename)
        tempdict = {}

        with open(filename) as f:
            code = compile(f.read(), filename, 'exec')
            exec(code, tempdict)

        settings.update((k, v) for k, v in tempdict.items()
                        if k not in ['__builtins__'])

        # Make the paths relative to the settings file
        paths = ['source', 'destination', 'watermark']

        if os.path.isdir(join(settings_path, settings['theme'])) and \
                os.path.isdir(join(settings_path, settings['theme'],
                                   'templates')):
            paths.append('theme')

        for p in paths:
            path = settings[p]
            if path and not isabs(path):
                settings[p] = abspath(normpath(join(settings_path, path)))
                logger.debug("Rewrite %s : %s -> %s", p, path, settings[p])

    for key in ('img_size', 'thumb_size', 'video_size'):
        w, h = settings[key]
        if h > w:
            settings[key] = (h, w)
            logger.warning("The %s setting should be specified with the "
                           "largest value first.", key)

    if not settings['img_processor']:
        logger.info('No Processor, images will not be resized')

    logger.debug('Settings:\n%s', pformat(settings, width=120))
    return settings

Read settings from a config file in the source_dir root .


def generate_media_pages(gallery):
    '''Generates and writes the media pages for all media in the gallery'''

    writer = PageWriter(gallery.settings, index_title=gallery.title)

    for album in gallery.albums.values():
        medias = album.medias
        next_medias = medias[1:] + [None]
        previous_medias = [None] + medias[:-1]

        # The media group allows us to easily get next and previous links
        media_groups = zip(medias, next_medias, previous_medias)

        for media_group in media_groups:
            writer.write(album, media_group)

def generate_media_pages ( gallery ) : writer = PageWriter ( gallery . settings , index_title = gallery . title ) for album in gallery . albums . values ( ) : medias = album . medias next_medias = medias [ 1 : ] + [ None ] previous_medias = [ None ] + medias [ : - 1 ] media_groups = zip ( medias , next_medias , previous_medias ) for media_group in media_groups : writer . write ( album , media_group )


def write(self, album, media_group):
        ''' Generate the media page and save it '''

        from sigal import __url__ as sigal_link
        file_path = os.path.join(album.dst_path, media_group[0].filename)

        page = self.template.render({
            'album': album,
            'media': media_group[0],
            'previous_media': media_group[-1],
            'next_media': media_group[1],
            'index_title': self.index_title,
            'settings': self.settings,
            'sigal_link': sigal_link,
            'theme': {'name': os.path.basename(self.theme),
                      'url': url_from_path(os.path.relpath(self.theme_path,
                                                           album.dst_path))},
        })

        output_file = "%s.html" % file_path

        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(page)

def write ( self , album , media_group ) : from sigal import __url__ as sigal_link file_path = os . path . join ( album . dst_path , media_group [ 0 ] . filename ) page = self . template . render ( { 'album' : album , 'media' : media_group [ 0 ] , 'previous_media' : media_group [ - 1 ] , 'next_media' : media_group [ 1 ] , 'index_title' : self . index_title , 'settings' : self . settings , 'sigal_link' : sigal_link , 'theme' : { 'name' : os . path . basename ( self . theme ) , 'url' : url_from_path ( os . path . relpath ( self . theme_path , album . dst_path ) ) } , } ) output_file = "%s.html" % file_path with open ( output_file , 'w' , encoding = 'utf-8' ) as f : f . write ( page )


def cleanup_directory(config_data):
    """
    Asks user for removal of project directory and eventually removes it
    """
    if os.path.exists(config_data.project_directory):
        choice = False
        if config_data.noinput is False and not config_data.verbose:
            choice = query_yes_no(
                'The installation failed.\n'
                'Do you want to clean up by removing {0}?\n'
                '\tWarning: this will delete all files in:\n'
                '\t\t{0}\n'
                'Do you want to cleanup?'.format(
                    os.path.abspath(config_data.project_directory)
                ),
                'no'
            )
        else:
            sys.stdout.write('The installation has failed.\n')
        if config_data.skip_project_dir_check is False and (choice or
                                                            (config_data.noinput and
                                                             config_data.delete_project_dir)):
            sys.stdout.write('Removing everything under {0}\n'.format(
                os.path.abspath(config_data.project_directory)
            ))
            shutil.rmtree(config_data.project_directory, True)


def validate_project(project_name):
    """
    Check the defined project name against keywords, builtins and existing
    modules to avoid name clashing
    """
    if '-' in project_name:
        return None
    if keyword.iskeyword(project_name):
        return None
    if project_name in dir(__builtins__):
        return None
    try:
        __import__(project_name)
        return None
    except ImportError:
        return project_name

def validate_project ( project_name ) : if '-' in project_name : return None if keyword . iskeyword ( project_name ) : return None if project_name in dir ( __builtins__ ) : return None try : __import__ ( project_name ) return None except ImportError : return project_name

Check the defined project name against keywords builtins and existing modules to avoid name clashing


def _manage_args(parser,  args):
    """
    Checks and validate provided input
    """
    for item in data.CONFIGURABLE_OPTIONS:
        action = parser._option_string_actions[item]
        choices = default = ''
        input_value = getattr(args, action.dest)
        new_val = None
        # cannot count this until we find a way to test input
        if not args.noinput:  # pragma: no cover
            if action.choices:
                choices = ' (choices: {0})'.format(', '.join(action.choices))
            if input_value:
                if type(input_value) == list:
                    default = ' [default {0}]'.format(', '.join(input_value))
                else:
                    default = ' [default {0}]'.format(input_value)

            while not new_val:
                prompt = '{0}{1}{2}: '.format(action.help, choices, default)
                if action.choices in ('yes', 'no'):
                    new_val = utils.query_yes_no(prompt)
                else:
                    new_val = compat.input(prompt)
                new_val = compat.clean(new_val)
                if not new_val and input_value:
                    new_val = input_value
                if new_val and action.dest == 'templates':
                    if new_val != 'no' and not os.path.isdir(new_val):
                        sys.stdout.write('Given directory does not exists, retry\n')
                        new_val = False
                if new_val and action.dest == 'db':
                    action(parser, args, new_val, action.option_strings)
                    new_val = getattr(args, action.dest)
        else:
            if not input_value and action.required:
                raise ValueError(
                    'Option {0} is required when in no-input mode'.format(action.dest)
                )
            new_val = input_value
            if action.dest == 'db':
                action(parser, args, new_val, action.option_strings)
                new_val = getattr(args, action.dest)
        if action.dest == 'templates' and (new_val == 'no' or not os.path.isdir(new_val)):
            new_val = False
        if action.dest in ('bootstrap', 'starting_page'):
            new_val = (new_val == 'yes')
        setattr(args, action.dest, new_val)
    return args


def supported_versions(django, cms):
    """
    Convert numeric and literal version information to numeric format
    """
    cms_version = None
    django_version = None

    try:
        cms_version = Decimal(cms)
    except (ValueError, InvalidOperation):
        try:
            cms_version = CMS_VERSION_MATRIX[str(cms)]
        except KeyError:
            pass

    try:
        django_version = Decimal(django)
    except (ValueError, InvalidOperation):
        try:
            django_version = DJANGO_VERSION_MATRIX[str(django)]
        except KeyError:  # pragma: no cover
            pass

    try:
        if (
                cms_version and django_version and
                not (LooseVersion(VERSION_MATRIX[compat.unicode(cms_version)][0]) <=
                     LooseVersion(compat.unicode(django_version)) <=
                     LooseVersion(VERSION_MATRIX[compat.unicode(cms_version)][1]))
        ):
            raise RuntimeError(
                'Django and django CMS versions doesn\'t match: '
                'Django {0} is not supported by django CMS {1}'.format(django_version, cms_version)
            )
    except KeyError:
        raise RuntimeError(
            'Django and django CMS versions doesn\'t match: '
            'Django {0} is not supported by django CMS {1}'.format(django_version, cms_version)
        )
    return (
        compat.unicode(django_version) if django_version else django_version,
        compat.unicode(cms_version) if cms_version else cms_version
    )


def less_than_version(value):
    """
    Converts the current version to the next one for inserting into requirements
    in the ' < version' format
    """
    items = list(map(int, str(value).split('.')))
    if len(items) == 1:
        items.append(0)
    items[1] += 1
    if value == '1.11':
        return '2.0'
    else:
        return '.'.join(map(str, items))

def less_than_version ( value ) : items = list ( map ( int , str ( value ) . split ( '.' ) ) ) if len ( items ) == 1 : items . append ( 0 ) items [ 1 ] += 1 if value == '1.11' : return '2.0' else : return '.' . join ( map ( str , items ) )

Converts the current version to the next one for inserting into requirements in the < version format


def parse_config_file(parser, stdin_args):
    """Parse config file.

    Returns a list of additional args.
    """
    config_args = []

    # Temporary switch required args and save them to restore.
    required_args = []
    for action in parser._actions:
        if action.required:
            required_args.append(action)
            action.required = False

    parsed_args = parser.parse_args(stdin_args)

    # Restore required args.
    for action in required_args:
        action.required = True

    if not parsed_args.config_file:
        return config_args

    config = ConfigParser()
    if not config.read(parsed_args.config_file):
        sys.stderr.write('Config file "{0}" doesn\'t exists\n'.format(parsed_args.config_file))
        sys.exit(7)  # It isn't used anywhere.

    config_args = _convert_config_to_stdin(config, parser)
    return config_args

def
parse_config_file
(
parser
,
stdin_args
)
:
config_args
=
[
]
required_args
=
[
]
for
action
in
parser
.
_actions
:
if
action
.
required
:
required_args
.
append
(
action
)
action
.
required
=
False
parsed_args
=
parser
.
parse_args
(
stdin_args
)
for
action
in
required_args
:
action
.
required
=
True
if
not
parsed_args
.
config_file
:
return
config_args
config
=
ConfigParser
(
)
if
not
config
.
read
(
parsed_args
.
config_file
)
:
sys
.
stderr
.
write
(
'Config file "{0}" doesn\'t exists\n'
.
format
(
parsed_args
.
config_file
)
)
sys
.
exit
(
7
)
config_args
=
_convert_config_to_stdin
(
config
,
parser
)
return
config_args


def dump_config_file(filename, args, parser=None):
    """Dump args to config file."""
    config = ConfigParser()
    config.add_section(SECTION)
    if parser is None:
        for attr in args:
            config.set(SECTION, attr, args.attr)
    else:
        keys_empty_values_not_pass = (
            '--extra-settings', '--languages', '--requirements', '--template', '--timezone')

        # positionals._option_string_actions
        for action in parser._actions:
            if action.dest in ('help', 'config_file', 'config_dump', 'project_name'):
                continue

            keyp = action.option_strings[0]
            option_name = keyp.lstrip('-')
            option_value = getattr(args, action.dest)
            if any([i for i in keys_empty_values_not_pass if i in action.option_strings]):
                if action.dest == 'languages':
                    if len(option_value) == 1 and option_value[0] == 'en':
                        config.set(SECTION, option_name, '')
                    else:
                        config.set(SECTION, option_name, ','.join(option_value))
                else:
                    config.set(SECTION, option_name, option_value if option_value else '')
            elif action.choices == ('yes', 'no'):
                config.set(SECTION, option_name, 'yes' if option_value else 'no')
            elif action.dest == 'templates':
                config.set(SECTION, option_name, option_value if option_value else 'no')
            elif action.dest == 'cms_version':
                version = ('stable' if option_value == CMS_VERSION_MATRIX['stable']
                           else option_value)
                config.set(SECTION, option_name, version)
            elif action.dest == 'django_version':
                version = ('stable' if option_value == DJANGO_VERSION_MATRIX['stable']
                           else option_value)
                config.set(SECTION, option_name, version)
            elif action.const:
                config.set(SECTION, option_name, 'true' if option_value else 'false')
            else:
                config.set(SECTION, option_name, str(option_value))
    with open(filename, 'w') as fp:
        config.write(fp)

def dump_config_file ( filename , args , parser = None ) : config = ConfigParser ( ) config . add_section ( SECTION ) if parser is None : for attr in args : config . set ( SECTION , attr , args . attr ) else : keys_empty_values_not_pass = ( '--extra-settings' , '--languages' , '--requirements' , '--template' , '--timezone' ) for action in parser . _actions : if action . dest in ( 'help' , 'config_file' , 'config_dump' , 'project_name' ) : continue keyp = action . option_strings [ 0 ] option_name = keyp . lstrip ( '-' ) option_value = getattr ( args , action . dest ) if any ( [ i for i in keys_empty_values_not_pass if i in action . option_strings ] ) : if action . dest == 'languages' : if len ( option_value ) == 1 and option_value [ 0 ] == 'en' : config . set ( SECTION , option_name , '' ) else : config . set ( SECTION , option_name , ',' . join ( option_value ) ) else : config . set ( SECTION , option_name , option_value if option_value else '' ) elif action . choices == ( 'yes' , 'no' ) : config . set ( SECTION , option_name , 'yes' if option_value else 'no' ) elif action . dest == 'templates' : config . set ( SECTION , option_name , option_value if option_value else 'no' ) elif action . dest == 'cms_version' : version = ( 'stable' if option_value == CMS_VERSION_MATRIX [ 'stable' ] else option_value ) config . set ( SECTION , option_name , version ) elif action . dest == 'django_version' : version = ( 'stable' if option_value == DJANGO_VERSION_MATRIX [ 'stable' ] else option_value ) config . set ( SECTION , option_name , version ) elif action . const : config . set ( SECTION , option_name , 'true' if option_value else 'false' ) else : config . set ( SECTION , option_name , str ( option_value ) ) with open ( filename , 'w' ) as fp : config . write ( fp )


def _convert_config_to_stdin(config, parser):
    """Convert config options to stdin args.

    Especially boolean values, for more information
    @see https://docs.python.org/3.4/library/configparser.html#supported-datatypes
    """
    keys_empty_values_not_pass = (
        '--extra-settings', '--languages', '--requirements', '--template', '--timezone')
    args = []
    for key, val in config.items(SECTION):
        keyp = '--{0}'.format(key)
        action = parser._option_string_actions[keyp]

        if action.const:
            try:
                if config.getboolean(SECTION, key):
                    args.append(keyp)
            except ValueError:
                args.extend([keyp, val])  # Pass it as is to get the error from ArgumentParser.
        elif any([i for i in keys_empty_values_not_pass if i in action.option_strings]):
            # Some keys with empty values shouldn't be passed into args to use their defaults
            # from ArgumentParser.
            if val != '':
                args.extend([keyp, val])
        else:
            args.extend([keyp, val])

    return args

def _convert_config_to_stdin ( config , parser ) : keys_empty_values_not_pass = ( '--extra-settings' , '--languages' , '--requirements' , '--template' , '--timezone' ) args = [ ] for key , val in config . items ( SECTION ) : keyp = '--{0}' . format ( key ) action = parser . _option_string_actions [ keyp ] if action . const : try : if config . getboolean ( SECTION , key ) : args . append ( keyp ) except ValueError : args . extend ( [ keyp , val ] ) elif any ( [ i for i in keys_empty_values_not_pass if i in action . option_strings ] ) : if val != '' : args . extend ( [ keyp , val ] ) else : args . extend ( [ keyp , val ] ) return args


def create_project(config_data):
    """
    Call django-admin to create the project structure

    :param config_data: configuration data
    """
    env = deepcopy(dict(os.environ))
    env[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
    env[str('PYTHONPATH')] = str(os.pathsep.join(map(shlex_quote, sys.path)))
    kwargs = {}
    args = []
    if config_data.template:
        kwargs['template'] = config_data.template
    args.append(config_data.project_name)
    if config_data.project_directory:
        args.append(config_data.project_directory)
        if not os.path.exists(config_data.project_directory):
            os.makedirs(config_data.project_directory)
    base_cmd = 'django-admin.py'
    start_cmds = [os.path.join(os.path.dirname(sys.executable), base_cmd)]
    start_cmd_pnodes = ['Scripts']
    start_cmds.extend([
        os.path.join(os.path.dirname(sys.executable), pnode, base_cmd)
        for pnode in start_cmd_pnodes
    ])
    start_cmd = [base_cmd]
    for p in start_cmds:
        if os.path.exists(p):
            start_cmd = [sys.executable, p]
            break
    cmd_args = start_cmd + ['startproject'] + args
    if config_data.verbose:
        sys.stdout.write('Project creation command: {0}\n'.format(' '.join(cmd_args)))
    try:
        output = subprocess.check_output(cmd_args, stderr=subprocess.STDOUT)
        sys.stdout.write(output.decode('utf-8'))
    except subprocess.CalledProcessError as e:  # pragma: no cover
        if config_data.verbose:
            sys.stdout.write(e.output.decode('utf-8'))
        raise

def
create_project
(
config_data
)
:
env
=
deepcopy
(
dict
(
os
.
environ
)
)
env
[
str
(
'DJANGO_SETTINGS_MODULE'
)
]
=
str
(
'{0}.settings'
.
format
(
config_data
.
project_name
)
)
env
[
str
(
'PYTHONPATH'
)
]
=
str
(
os
.
pathsep
.
join
(
map
(
shlex_quote
,
sys
.
path
)
)
)
kwargs
=
{
}
args
=
[
]
if
config_data
.
template
:
kwargs
[
'template'
]
=
config_data
.
template
args
.
append
(
config_data
.
project_name
)
if
config_data
.
project_directory
:
args
.
append
(
config_data
.
project_directory
)
if
not
os
.
path
.
exists
(
config_data
.
project_directory
)
:
os
.
makedirs
(
config_data
.
project_directory
)
base_cmd
=
'django-admin.py'
start_cmds
=
[
os
.
path
.
join
(
os
.
path
.
dirname
(
sys
.
executable
)
,
base_cmd
)
]
start_cmd_pnodes
=
[
'Scripts'
]
start_cmds
.
extend
(
[
os
.
path
.
join
(
os
.
path
.
dirname
(
sys
.
executable
)
,
pnode
,
base_cmd
)
for
pnode
in
start_cmd_pnodes
]
)
start_cmd
=
[
base_cmd
]
for
p
in
start_cmds
:
if
os
.
path
.
exists
(
p
)
:
start_cmd
=
[
sys
.
executable
,
p
]
break
cmd_args
=
start_cmd
+
[
'startproject'
]
+
args
if
config_data
.
verbose
:
sys
.
stdout
.
write
(
'Project creation command: {0}\n'
.
format
(
' '
.
join
(
cmd_args
)
)
)
try
:
output
=
subprocess
.
check_output
(
cmd_args
,
stderr
=
subprocess
.
STDOUT
)
sys
.
stdout
.
write
(
output
.
decode
(
'utf-8'
)
)
except
subprocess
.
CalledProcessError
as
e
:
if
config_data
.
verbose
:
sys
.
stdout
.
write
(
e
.
output
.
decode
(
'utf-8'
)
)
raise

Call django - admin to create the project structure


def _install_aldryn(config_data):  # pragma: no cover
    """
    Install aldryn boilerplate

    :param config_data: configuration data
    """
    import requests
    media_project = os.path.join(config_data.project_directory, 'dist', 'media')
    static_main = False
    static_project = os.path.join(config_data.project_directory, 'dist', 'static')
    template_target = os.path.join(config_data.project_directory, 'templates')
    tmpdir = tempfile.mkdtemp()
    aldrynzip = requests.get(data.ALDRYN_BOILERPLATE)
    zip_open = zipfile.ZipFile(BytesIO(aldrynzip.content))
    zip_open.extractall(path=tmpdir)
    for component in os.listdir(os.path.join(tmpdir, 'aldryn-boilerplate-standard-master')):
        src = os.path.join(tmpdir, 'aldryn-boilerplate-standard-master', component)
        dst = os.path.join(config_data.project_directory, component)
        if os.path.isfile(src):
            shutil.copy(src, dst)
        else:
            shutil.copytree(src, dst)
    shutil.rmtree(tmpdir)
    return media_project, static_main, static_project, template_target

def _install_aldryn ( config_data ) : import requests media_project = os . path . join ( config_data . project_directory , 'dist' , 'media' ) static_main = False static_project = os . path . join ( config_data . project_directory , 'dist' , 'static' ) template_target = os . path . join ( config_data . project_directory , 'templates' ) tmpdir = tempfile . mkdtemp ( ) aldrynzip = requests . get ( data . ALDRYN_BOILERPLATE ) zip_open = zipfile . ZipFile ( BytesIO ( aldrynzip . content ) ) zip_open . extractall ( path = tmpdir ) for component in os . listdir ( os . path . join ( tmpdir , 'aldryn-boilerplate-standard-master' ) ) : src = os . path . join ( tmpdir , 'aldryn-boilerplate-standard-master' , component ) dst = os . path . join ( config_data . project_directory , component ) if os . path . isfile ( src ) : shutil . copy ( src , dst ) else : shutil . copytree ( src , dst ) shutil . rmtree ( tmpdir ) return media_project , static_main , static_project , template_target


def setup_database(config_data):
    """
    Run the migrate command to create the database schema

    :param config_data: configuration data
    """
    with chdir(config_data.project_directory):
        env = deepcopy(dict(os.environ))
        env[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
        env[str('PYTHONPATH')] = str(os.pathsep.join(map(shlex_quote, sys.path)))
        commands = []

        commands.append(
            [sys.executable, '-W', 'ignore', 'manage.py', 'migrate'],
        )

        if config_data.verbose:
            sys.stdout.write(
                'Database setup commands: {0}\n'.format(
                    ', '.join([' '.join(cmd) for cmd in commands])
                )
            )
        for command in commands:
            try:
                output = subprocess.check_output(
                    command, env=env, stderr=subprocess.STDOUT
                )
                sys.stdout.write(output.decode('utf-8'))
            except subprocess.CalledProcessError as e:  # pragma: no cover
                if config_data.verbose:
                    sys.stdout.write(e.output.decode('utf-8'))
                raise

        if not config_data.no_user:
            sys.stdout.write('Creating admin user\n')
            if config_data.noinput:
                create_user(config_data)
            else:
                subprocess.check_call(' '.join(
                    [sys.executable, '-W', 'ignore', 'manage.py', 'createsuperuser']
                ), shell=True, stderr=subprocess.STDOUT)

def
setup_database
(
config_data
)
:
with
chdir
(
config_data
.
project_directory
)
:
env
=
deepcopy
(
dict
(
os
.
environ
)
)
env
[
str
(
'DJANGO_SETTINGS_MODULE'
)
]
=
str
(
'{0}.settings'
.
format
(
config_data
.
project_name
)
)
env
[
str
(
'PYTHONPATH'
)
]
=
str
(
os
.
pathsep
.
join
(
map
(
shlex_quote
,
sys
.
path
)
)
)
commands
=
[
]
commands
.
append
(
[
sys
.
executable
,
'-W'
,
'ignore'
,
'manage.py'
,
'migrate'
]
,
)
if
config_data
.
verbose
:
sys
.
stdout
.
write
(
'Database setup commands: {0}\n'
.
format
(
', '
.
join
(
[
' '
.
join
(
cmd
)
for
cmd
in
commands
]
)
)
)
for
command
in
commands
:
try
:
output
=
subprocess
.
check_output
(
command
,
env
=
env
,
stderr
=
subprocess
.
STDOUT
)
sys
.
stdout
.
write
(
output
.
decode
(
'utf-8'
)
)
except
subprocess
.
CalledProcessError
as
e
:
if
config_data
.
verbose
:
sys
.
stdout
.
write
(
e
.
output
.
decode
(
'utf-8'
)
)
raise
if
not
config_data
.
no_user
:
sys
.
stdout
.
write
(
'Creating admin user\n'
)
if
config_data
.
noinput
:
create_user
(
config_data
)
else
:
subprocess
.
check_call
(
' '
.
join
(
[
sys
.
executable
,
'-W'
,
'ignore'
,
'manage.py'
,
'createsuperuser'
]
)
,
shell
=
True
,
stderr
=
subprocess
.
STDOUT
)


def create_user(config_data):
    """
    Create admin user without user input

    :param config_data: configuration data
    """
    with chdir(os.path.abspath(config_data.project_directory)):
        env = deepcopy(dict(os.environ))
        env[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
        env[str('PYTHONPATH')] = str(os.pathsep.join(map(shlex_quote, sys.path)))
        subprocess.check_call(
            [sys.executable, 'create_user.py'], env=env, stderr=subprocess.STDOUT
        )
        for ext in ['py', 'pyc']:
            try:
                os.remove('create_user.{0}'.format(ext))
            except OSError:
                pass

def create_user ( config_data ) : with chdir ( os . path . abspath ( config_data . project_directory ) ) : env = deepcopy ( dict ( os . environ ) ) env [ str ( 'DJANGO_SETTINGS_MODULE' ) ] = str ( '{0}.settings' . format ( config_data . project_name ) ) env [ str ( 'PYTHONPATH' ) ] = str ( os . pathsep . join ( map ( shlex_quote , sys . path ) ) ) subprocess . check_call ( [ sys . executable , 'create_user.py' ] , env = env , stderr = subprocess . STDOUT ) for ext in [ 'py' , 'pyc' ] : try : os . remove ( 'create_user.{0}' . format ( ext ) ) except OSError : pass


def sox(args):
    '''Pass an argument list to SoX.

    Parameters
    ----------
    args : iterable
        Argument list for SoX. The first item can, but does not
        need to, be 'sox'.

    Returns:
    --------
    status : bool
        True on success.

    '''
    if args[0].lower() != "sox":
        args.insert(0, "sox")
    else:
        args[0] = "sox"

    try:
        logger.info("Executing: %s", ' '.join(args))

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

        out, err = process_handle.communicate()
        out = out.decode("utf-8")
        err = err.decode("utf-8")

        status = process_handle.returncode
        return status, out, err

    except OSError as error_msg:
        logger.error("OSError: SoX failed! %s", error_msg)
    except TypeError as error_msg:
        logger.error("TypeError: %s", error_msg)
    return 1, None, None

def
sox
(
args
)
:
if
args
[
0
]
.
lower
(
)
!=
"sox"
:
args
.
insert
(
0
,
"sox"
)
else
:
args
[
0
]
=
"sox"
try
:
logger
.
info
(
"Executing: %s"
,
' '
.
join
(
args
)
)
process_handle
=
subprocess
.
Popen
(
args
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
)
out
,
err
=
process_handle
.
communicate
(
)
out
=
out
.
decode
(
"utf-8"
)
err
=
err
.
decode
(
"utf-8"
)
status
=
process_handle
.
returncode
return
status
,
out
,
err
except
OSError
as
error_msg
:
logger
.
error
(
"OSError: SoX failed! %s"
,
error_msg
)
except
TypeError
as
error_msg
:
logger
.
error
(
"TypeError: %s"
,
error_msg
)
return
1
,
,


def _get_valid_formats():
    ''' Calls SoX help for a lists of audio formats available with the current
    install of SoX.

    Returns:
    --------
    formats : list
        List of audio file extensions that SoX can process.

    '''
    if NO_SOX:
        return []

    so = subprocess.check_output(['sox', '-h'])
    if type(so) is not str:
        so = str(so, encoding='UTF-8')
    so = so.split('\n')
    idx = [i for i in range(len(so)) if 'AUDIO FILE FORMATS:' in so[i]][0]
    formats = so[idx].split(' ')[3:]

    return formats

def
_get_valid_formats
(
)
:
if
NO_SOX
:
return
[
]
so
=
subprocess
.
check_output
(
[
'sox'
,
'-h'
]
)
if
type
(
so
)
is
not
str
:
so
=
str
(
so
,
encoding
=
'UTF-8'
)
so
=
so
.
split
(
'\n'
)
idx
=
[
i
for
i
in
range
(
len
(
so
)
)
if
'AUDIO FILE FORMATS:'
in
so
[
i
]
]
[
0
]
formats
=
so
[
idx
]
.
split
(
' '
)
[
3
:
]
return
formats

Calls SoX help for a lists of audio formats available with the current install of SoX .


def soxi(filepath, argument):
    ''' Base call to SoXI.

    Parameters
    ----------
    filepath : str
        Path to audio file.

    argument : str
        Argument to pass to SoXI.

    Returns
    -------
    shell_output : str
        Command line output of SoXI
    '''

    if argument not in SOXI_ARGS:
        raise ValueError("Invalid argument '{}' to SoXI".format(argument))

    args = ['sox', '--i']
    args.append("-{}".format(argument))
    args.append(filepath)

    try:
        shell_output = subprocess.check_output(
            args,
            stderr=subprocess.PIPE
        )
    except CalledProcessError as cpe:
        logger.info("SoXI error message: {}".format(cpe.output))
        raise SoxiError("SoXI failed with exit code {}".format(cpe.returncode))

    shell_output = shell_output.decode("utf-8")

    return str(shell_output).strip('\n')

def
soxi
(
filepath
,
argument
)
:
if
argument
not
in
SOXI_ARGS
:
raise
ValueError
(
"Invalid argument '{}' to SoXI"
.
format
(
argument
)
)
args
=
[
'sox'
,
'--i'
]
args
.
append
(
"-{}"
.
format
(
argument
)
)
args
.
append
(
filepath
)
try
:
shell_output
=
subprocess
.
check_output
(
args
,
stderr
=
subprocess
.
PIPE
)
except
CalledProcessError
as
cpe
:
logger
.
info
(
"SoXI error message: {}"
.
format
(
cpe
.
output
)
)
raise
SoxiError
(
"SoXI failed with exit code {}"
.
format
(
cpe
.
returncode
)
)
shell_output
=
shell_output
.
decode
(
"utf-8"
)
return
str
(
shell_output
)
.
strip
(
'\n'
)


def play(args):
    '''Pass an argument list to play.

    Parameters
    ----------
    args : iterable
        Argument list for play. The first item can, but does not
        need to, be 'play'.

    Returns:
    --------
    status : bool
        True on success.

    '''
    if args[0].lower() != "play":
        args.insert(0, "play")
    else:
        args[0] = "play"

    try:
        logger.info("Executing: %s", " ".join(args))
        process_handle = subprocess.Popen(
            args, stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )

        status = process_handle.wait()
        if process_handle.stderr is not None:
            logger.info(process_handle.stderr)

        if status == 0:
            return True
        else:
            logger.info("Play returned with error code %s", status)
            return False
    except OSError as error_msg:
        logger.error("OSError: Play failed! %s", error_msg)
    except TypeError as error_msg:
        logger.error("TypeError: %s", error_msg)
    return False

def
play
(
args
)
:
if
args
[
0
]
.
lower
(
)
!=
"play"
:
args
.
insert
(
0
,
"play"
)
else
:
args
[
0
]
=
"play"
try
:
logger
.
info
(
"Executing: %s"
,
" "
.
join
(
args
)
)
process_handle
=
subprocess
.
Popen
(
args
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
)
status
=
process_handle
.
wait
(
)
if
process_handle
.
stderr
is
not
:
logger
.
info
(
process_handle
.
stderr
)
if
status
==
0
:
return
True
else
:
logger
.
info
(
"Play returned with error code %s"
,
status
)
return
False
except
OSError
as
error_msg
:
logger
.
error
(
"OSError: Play failed! %s"
,
error_msg
)
except
TypeError
as
error_msg
:
logger
.
error
(
"TypeError: %s"
,
error_msg
)
return
False


def _validate_file_formats(input_filepath_list, combine_type):
    '''Validate that combine method can be performed with given files.
    Raises IOError if input file formats are incompatible.
    '''
    _validate_sample_rates(input_filepath_list, combine_type)

    if combine_type == 'concatenate':
        _validate_num_channels(input_filepath_list, combine_type)

Validate that combine method can be performed with given files . Raises IOError if input file formats are incompatible .


def _validate_sample_rates(input_filepath_list, combine_type):
    ''' Check if files in input file list have the same sample rate
    '''
    sample_rates = [
        file_info.sample_rate(f) for f in input_filepath_list
    ]
    if not core.all_equal(sample_rates):
        raise IOError(
            "Input files do not have the same sample rate. The {} combine "
            "type requires that all files have the same sample rate"
            .format(combine_type)
        )


def _validate_num_channels(input_filepath_list, combine_type):
    ''' Check if files in input file list have the same number of channels
    '''
    channels = [
        file_info.channels(f) for f in input_filepath_list
    ]
    if not core.all_equal(channels):
        raise IOError(
            "Input files do not have the same number of channels. The "
            "{} combine type requires that all files have the same "
            "number of channels"
            .format(combine_type)
        )


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:
        n_volumes = len(input_volumes)
        if n_volumes < n_inputs:
            logger.warning(
                'Volumes were only specified for %s out of %s files.'
                'The last %s files will remain at their original volumes.',
                n_volumes, n_inputs, n_inputs - n_volumes
            )
            vols = input_volumes + [1] * (n_inputs - n_volumes)
        elif n_volumes > n_inputs:
            logger.warning(
                '%s volumes were specified but only %s input files exist.'
                'The last %s volumes will be ignored.',
                n_volumes, n_inputs, n_volumes - n_inputs
            )
            vols = input_volumes[:n_inputs]
        else:
            vols = [v for v in input_volumes]

    # 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:
            logger.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:
            logger.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


def _build_input_args(input_filepath_list, input_format_list):
    ''' Builds input arguments by stitching input filepaths and input
    formats together.
    '''
    if len(input_format_list) != len(input_filepath_list):
        raise ValueError(
            "input_format_list & input_filepath_list are not the same size"
        )

    input_args = []
    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)

    return input_args

Builds input arguments by stitching input filepaths and input formats together .


def _validate_volumes(input_volumes):
    '''Check input_volumes contains a valid list of volumes.

    Parameters
    ----------
    input_volumes : list
        list of volume values. Castable to numbers.

    '''
    if not (input_volumes is None or isinstance(input_volumes, list)):
        raise TypeError("input_volumes must be None or a list.")

    if isinstance(input_volumes, list):
        for vol in input_volumes:
            if not core.is_number(vol):
                raise ValueError(
                    "Elements of input_volumes must be numbers: found {}"
                    .format(vol)
                )

Check input_volumes contains a valid list of volumes .


def silent(input_filepath, threshold=0.001):
    '''
    Determine if an input file is silent.

    Parameters
    ----------
    input_filepath : str
        The input filepath.
    threshold : float
        Threshold for determining silence

    Returns
    -------
    is_silent : bool
        True if file is determined silent.
    '''
    validate_input_file(input_filepath)
    stat_dictionary = stat(input_filepath)
    mean_norm = stat_dictionary['Mean    norm']
    if mean_norm is not float('nan'):
        if mean_norm >= threshold:
            return False
        else:
            return True
    else:
        return True

def
silent
(
input_filepath
,
threshold
=
0.001
)
:
validate_input_file
(
input_filepath
)
stat_dictionary
=
stat
(
input_filepath
)
mean_norm
=
stat_dictionary
[
'Mean    norm'
]
if
mean_norm
is
not
float
(
'nan'
)
:
if
mean_norm
>=
threshold
:
return
False
else
:
return
True
else
:
return
True


def validate_input_file(input_filepath):
    '''Input file validation function. Checks that file exists and can be
    processed by SoX.

    Parameters
    ----------
    input_filepath : str
        The input filepath.

    '''
    if not os.path.exists(input_filepath):
        raise IOError(
            "input_filepath {} does not exist.".format(input_filepath)
        )
    ext = file_extension(input_filepath)
    if ext not in VALID_FORMATS:
        logger.info("Valid formats: %s", " ".join(VALID_FORMATS))
        logger.warning(
            "This install of SoX cannot process .{} files.".format(ext)
        )

Input file validation function . Checks that file exists and can be processed by SoX .


def validate_input_file_list(input_filepath_list):
    '''Input file list validation function. Checks that object is a list and
    contains valid filepaths that can be processed by SoX.

    Parameters
    ----------
    input_filepath_list : list
        A list of filepaths.

    '''
    if not isinstance(input_filepath_list, list):
        raise TypeError("input_filepath_list must be a list.")
    elif len(input_filepath_list) < 2:
        raise ValueError("input_filepath_list must have at least 2 files.")

    for input_filepath in input_filepath_list:
        validate_input_file(input_filepath)

Input file list validation function . Checks that object is a list and contains valid filepaths that can be processed by SoX .


def validate_output_file(output_filepath):
    '''Output file validation function. Checks that file can be written, and
    has a valid file extension. Throws a warning if the path already exists,
    as it will be overwritten on build.

    Parameters
    ----------
    output_filepath : str
        The output filepath.

    Returns:
    --------
    output_filepath : str
        The output filepath.

    '''

    nowrite_conditions = [
        bool(os.path.dirname(output_filepath)) or\
            not os.access(os.getcwd(), os.W_OK),
        not os.access(os.path.dirname(output_filepath), os.W_OK)]

    if all(nowrite_conditions):
        raise IOError(
            "SoX cannot write to output_filepath {}".format(output_filepath)
        )

    ext = file_extension(output_filepath)
    if ext not in VALID_FORMATS:
        logger.info("Valid formats: %s", " ".join(VALID_FORMATS))
        logger.warning(
            "This install of SoX cannot process .{} files.".format(ext)
        )

    if os.path.exists(output_filepath):
        logger.warning(
            'output_file: %s already exists and will be overwritten on build',
            output_filepath
        )

Output file validation function . Checks that file can be written and has a valid file extension . Throws a warning if the path already exists as it will be overwritten on build .


def info(filepath):
    '''Get a dictionary of file information

    Parameters
    ----------
    filepath : str
        File path.

    Returns:
    --------
    info_dictionary : dict
        Dictionary of file information. Fields are:
            * channels
            * sample_rate
            * bitrate
            * duration
            * num_samples
            * encoding
            * silent
    '''
    info_dictionary = {
        'channels': channels(filepath),
        'sample_rate': sample_rate(filepath),
        'bitrate': bitrate(filepath),
        'duration': duration(filepath),
        'num_samples': num_samples(filepath),
        'encoding': encoding(filepath),
        'silent': silent(filepath)
    }
    return info_dictionary

def info ( filepath ) : info_dictionary = { 'channels' : channels ( filepath ) , 'sample_rate' : sample_rate ( filepath ) , 'bitrate' : bitrate ( filepath ) , 'duration' : duration ( filepath ) , 'num_samples' : num_samples ( filepath ) , 'encoding' : encoding ( filepath ) , 'silent' : silent ( filepath ) } return info_dictionary


def _stat_call(filepath):
    '''Call sox's stat function.

    Parameters
    ----------
    filepath : str
        File path.

    Returns
    -------
    stat_output : str
        Sox output from stderr.
    '''
    validate_input_file(filepath)
    args = ['sox', filepath, '-n', 'stat']
    _, _, stat_output = sox(args)
    return stat_output

def _stat_call ( filepath ) : validate_input_file ( filepath ) args = [ 'sox' , filepath , '-n' , 'stat' ] _ , _ , stat_output = sox ( args ) return stat_output


def _parse_stat(stat_output):
    '''Parse the string output from sox's stat function

    Parameters
    ----------
    stat_output : str
        Sox output from stderr.

    Returns
    -------
    stat_dictionary : dict
        Dictionary of audio statistics.
    '''
    lines = stat_output.split('\n')
    stat_dict = {}
    for line in lines:
        split_line = line.split(':')
        if len(split_line) == 2:
            key = split_line[0]
            val = split_line[1].strip(' ')
            try:
                val = float(val)
            except ValueError:
                val = None
            stat_dict[key] = val

    return stat_dict

def
_parse_stat
(
stat_output
)
:
lines
=
stat_output
.
split
(
'\n'
)
stat_dict
=
{
}
for
line
in
lines
:
split_line
=
line
.
split
(
':'
)
if
len
(
split_line
)
==
2
:
key
=
split_line
[
0
]
val
=
split_line
[
1
]
.
strip
(
' '
)
try
:
val
=
float
(
val
)
except
ValueError
:
val
=
stat_dict
[
key
]
=
val
return
stat_dict

Parse the string output from sox s stat function


def set_globals(self, dither=False, guard=False, multithread=False,
                    replay_gain=False, verbosity=2):
        '''Sets SoX's global arguments.
        Overwrites any previously set global arguments.
        If this function is not explicity called, globals are set to this
        function's defaults.

        Parameters
        ----------
        dither : bool, default=False
            If True, dithering is applied for low files with low bit rates.
        guard : bool, default=False
            If True, invokes the gain effect to guard against clipping.
        multithread : bool, default=False
            If True, each channel is processed in parallel.
        replay_gain : bool, default=False
            If True, applies replay-gain adjustment to input-files.
        verbosity : int, default=2
            SoX's verbosity level. One of:
                * 0 : No messages are shown at all
                * 1 : Only error messages are shown. These are generated if SoX
                    cannot complete the requested commands.
                * 2 : Warning messages are also shown. These are generated if
                    SoX can complete the requested commands, but not exactly
                    according to the requested command parameters, or if
                    clipping occurs.
                * 3 : Descriptions of SoX’s processing phases are also shown.
                    Useful for seeing exactly how SoX is processing your audio.
                * 4, >4 : Messages to help with debugging SoX are also shown.

        '''
        if not isinstance(dither, bool):
            raise ValueError('dither must be a boolean.')

        if not isinstance(guard, bool):
            raise ValueError('guard must be a boolean.')

        if not isinstance(multithread, bool):
            raise ValueError('multithread must be a boolean.')

        if not isinstance(replay_gain, bool):
            raise ValueError('replay_gain must be a boolean.')

        if verbosity not in VERBOSITY_VALS:
            raise ValueError(
                'Invalid value for VERBOSITY. Must be one {}'.format(
                    VERBOSITY_VALS)
            )

        global_args = []

        if not dither:
            global_args.append('-D')

        if guard:
            global_args.append('-G')

        if multithread:
            global_args.append('--multi-threaded')

        if replay_gain:
            global_args.append('--replay-gain')
            global_args.append('track')

        global_args.append('-V{}'.format(verbosity))

        self.globals = global_args
        return self

Sets SoX s global arguments . Overwrites any previously set global arguments . If this function is not explicity called globals are set to this function s defaults .


def biquad(self, b, a):
        '''Apply a biquad IIR filter with the given coefficients.

        Parameters
        ----------
        b : list of floats
            Numerator coefficients. Must be length 3
        a : list of floats
            Denominator coefficients. Must be length 3

        See Also
        --------
        fir, treble, bass, equalizer

        '''
        if not isinstance(b, list):
            raise ValueError('b must be a list.')

        if not isinstance(a, list):
            raise ValueError('a must be a list.')

        if len(b) != 3:
            raise ValueError('b must be a length 3 list.')

        if len(a) != 3:
            raise ValueError('a must be a length 3 list.')

        if not all([is_number(b_val) for b_val in b]):
            raise ValueError('all elements of b must be numbers.')

        if not all([is_number(a_val) for a_val in a]):
            raise ValueError('all elements of a must be numbers.')

        effect_args = [
            'biquad', '{:f}'.format(b[0]), '{:f}'.format(b[1]),
            '{:f}'.format(b[2]), '{:f}'.format(a[0]),
            '{:f}'.format(a[1]), '{:f}'.format(a[2])
        ]

        self.effects.extend(effect_args)
        self.effects_log.append('biquad')
        return self

def
biquad
(
self
,
b
,
a
)
:
if
not
isinstance
(
b
,
list
)
:
raise
ValueError
(
'b must be a list.'
)
if
not
isinstance
(
a
,
list
)
:
raise
ValueError
(
'a must be a list.'
)
if
len
(
b
)
!=
3
:
raise
ValueError
(
'b must be a length 3 list.'
)
if
len
(
a
)
!=
3
:
raise
ValueError
(
'a must be a length 3 list.'
)
if
not
all
(
[
is_number
(
b_val
)
for
b_val
in
b
]
)
:
raise
ValueError
(
'all elements of b must be numbers.'
)
if
not
all
(
[
is_number
(
a_val
)
for
a_val
in
a
]
)
:
raise
ValueError
(
'all elements of a must be numbers.'
)
effect_args
=
[
'biquad'
,
'{:f}'
.
format
(
b
[
0
]
)
,
'{:f}'
.
format
(
b
[
1
]
)
,
'{:f}'
.
format
(
b
[
2
]
)
,
'{:f}'
.
format
(
a
[
0
]
)
,
'{:f}'
.
format
(
a
[
1
]
)
,
'{:f}'
.
format
(
a
[
2
]
)
]
self
.
effects
.
extend
(
effect_args
)
self
.
effects_log
.
append
(
'biquad'
)
return
self

Apply a biquad IIR filter with the given coefficients .


def channels(self, n_channels):
        '''Change the number of channels in the audio signal. If decreasing the
        number of channels it mixes channels together, if increasing the number
        of channels it duplicates.

        Note: This overrides arguments used in the convert effect!

        Parameters
        ----------
        n_channels : int
            Desired number of channels.

        See Also
        --------
        convert

        '''
        if not isinstance(n_channels, int) or n_channels <= 0:
            raise ValueError('n_channels must be a positive integer.')

        effect_args = ['channels', '{}'.format(n_channels)]

        self.effects.extend(effect_args)
        self.effects_log.append('channels')
        return self

Change the number of channels in the audio signal . If decreasing the number of channels it mixes channels together if increasing the number of channels it duplicates .


def contrast(self, amount=75):
        '''Comparable with compression, this effect modifies an audio signal to
        make it sound louder.

        Parameters
        ----------
        amount : float
            Amount of enhancement between 0 and 100.

        See Also
        --------
        compand, mcompand

        '''
        if not is_number(amount) or amount < 0 or amount > 100:
            raise ValueError('amount must be a number between 0 and 100.')

        effect_args = ['contrast', '{:f}'.format(amount)]

        self.effects.extend(effect_args)
        self.effects_log.append('contrast')
        return self

Comparable with compression this effect modifies an audio signal to make it sound louder .


def convert(self, samplerate=None, n_channels=None, bitdepth=None):
        '''Converts output audio to the specified format.

        Parameters
        ----------
        samplerate : float, default=None
            Desired samplerate. If None, defaults to the same as input.
        n_channels : int, default=None
            Desired number of channels. If None, defaults to the same as input.
        bitdepth : int, default=None
            Desired bitdepth. If None, defaults to the same as input.

        See Also
        --------
        rate

        '''
        bitdepths = [8, 16, 24, 32, 64]
        if bitdepth is not None:
            if bitdepth not in bitdepths:
                raise ValueError(
                    "bitdepth must be one of {}.".format(str(bitdepths))
                )
            self.output_format.extend(['-b', '{}'.format(bitdepth)])
        if n_channels is not None:
            if not isinstance(n_channels, int) or n_channels <= 0:
                raise ValueError(
                    "n_channels must be a positive integer."
                )
            self.output_format.extend(['-c', '{}'.format(n_channels)])
        if samplerate is not None:
            if not is_number(samplerate) or samplerate <= 0:
                raise ValueError("samplerate must be a positive number.")
            self.rate(samplerate)
        return self

Converts output audio to the specified format .


def dcshift(self, shift=0.0):
        '''Apply a DC shift to the audio.

        Parameters
        ----------
        shift : float
            Amount to shift audio between -2 and 2. (Audio is between -1 and 1)

        See Also
        --------
        highpass

        '''
        if not is_number(shift) or shift < -2 or shift > 2:
            raise ValueError('shift must be a number between -2 and 2.')

        effect_args = ['dcshift', '{:f}'.format(shift)]

        self.effects.extend(effect_args)
        self.effects_log.append('dcshift')
        return self


def delay(self, positions):
        '''Delay one or more audio channels such that they start at the given
        positions.

        Parameters
        ----------
        positions: list of floats
            List of times (in seconds) to delay each audio channel.
            If fewer positions are given than the number of channels, the
            remaining channels will be unaffected.

        '''
        if not isinstance(positions, list):
            raise ValueError("positions must be a a list of numbers")

        if not all((is_number(p) and p >= 0) for p in positions):
            raise ValueError("positions must be positive nubmers")

        effect_args = ['delay']
        effect_args.extend(['{:f}'.format(p) for p in positions])

        self.effects.extend(effect_args)
        self.effects_log.append('delay')
        return self

def
delay
(
self
,
positions
)
:
if
not
isinstance
(
positions
,
list
)
:
raise
ValueError
(
"positions must be a a list of numbers"
)
if
not
all
(
(
is_number
(
p
)
and
p
>=
0
)
for
p
in
positions
)
:
raise
ValueError
(
"positions must be positive nubmers"
)
effect_args
=
[
'delay'
]
effect_args
.
extend
(
[
'{:f}'
.
format
(
p
)
for
p
in
positions
]
)
self
.
effects
.
extend
(
effect_args
)
self
.
effects_log
.
append
(
'delay'
)
return
self

Delay one or more audio channels such that they start at the given positions .


def downsample(self, factor=2):
        '''Downsample the signal by an integer factor. Only the first out of
        each factor samples is retained, the others are discarded.

        No decimation filter is applied. If the input is not a properly
        bandlimited baseband signal, aliasing will occur. This may be desirable
        e.g., for frequency translation.

        For a general resampling effect with anti-aliasing, see rate.

        Parameters
        ----------
        factor : int, default=2
            Downsampling factor.

        See Also
        --------
        rate, upsample

        '''
        if not isinstance(factor, int) or factor < 1:
            raise ValueError('factor must be a positive integer.')

        effect_args = ['downsample', '{}'.format(factor)]

        self.effects.extend(effect_args)
        self.effects_log.append('downsample')
        return self

def
downsample
(
self
,
factor
=
2
)
:
if
not
isinstance
(
factor
,
int
)
or
factor
<
1
:
raise
ValueError
(
'factor must be a positive integer.'
)
effect_args
=
[
'downsample'
,
'{}'
.
format
(
factor
)
]
self
.
effects
.
extend
(
effect_args
)
self
.
effects_log
.
append
(
'downsample'
)
return
self

Downsample the signal by an integer factor . Only the first out of each factor samples is retained the others are discarded .


def echo(self, gain_in=0.8, gain_out=0.9, n_echos=1, delays=[60],
             decays=[0.4]):
        '''Add echoing to the audio.

        Echoes are reflected sound and can occur naturally amongst mountains
        (and sometimes large buildings) when talking or shouting; digital echo
        effects emulate this behav- iour and are often used to help fill out
        the sound of a single instrument or vocal. The time differ- ence
        between the original signal and the reflection is the 'delay' (time),
        and the loudness of the reflected signal is the 'decay'. Multiple
        echoes can have different delays and decays.

        Parameters
        ----------
        gain_in : float, default=0.8
            Input volume, between 0 and 1
        gain_out : float, default=0.9
            Output volume, between 0 and 1
        n_echos : int, default=1
            Number of reflections
        delays : list, default=[60]
            List of delays in miliseconds
        decays : list, default=[0.4]
            List of decays, relative to gain in between 0 and 1

        See Also
        --------
        echos, reverb, chorus
        '''
        if not is_number(gain_in) or gain_in <= 0 or gain_in > 1:
            raise ValueError("gain_in must be a number between 0 and 1.")

        if not is_number(gain_out) or gain_out <= 0 or gain_out > 1:
            raise ValueError("gain_out must be a number between 0 and 1.")

        if not isinstance(n_echos, int) or n_echos <= 0:
            raise ValueError("n_echos must be a positive integer.")

        # validate delays
        if not isinstance(delays, list):
            raise ValueError("delays must be a list")

        if len(delays) != n_echos:
            raise ValueError("the length of delays must equal n_echos")

        if any((not is_number(p) or p <= 0) for p in delays):
            raise ValueError("the elements of delays must be numbers > 0")

        # validate decays
        if not isinstance(decays, list):
            raise ValueError("decays must be a list")

        if len(decays) != n_echos:
            raise ValueError("the length of decays must equal n_echos")
        if any((not is_number(p) or p <= 0 or p > 1) for p in decays):
            raise ValueError(
                "the elements of decays must be between 0 and 1"
            )

        effect_args = ['echo', '{:f}'.format(gain_in), '{:f}'.format(gain_out)]

        for i in range(n_echos):
            effect_args.extend([
                '{}'.format(delays[i]),
                '{}'.format(decays[i])
            ])

        self.effects.extend(effect_args)
        self.effects_log.append('echo')
        return self


def flanger(self, delay=0, depth=2, regen=0, width=71, speed=0.5,
                shape='sine', phase=25, interp='linear'):
        '''Apply a flanging effect to the audio.

        Parameters
        ----------
        delay : float, default=0
            Base delay (in miliseconds) between 0 and 30.
        depth : float, default=2
            Added swept delay (in miliseconds) between 0 and 10.
        regen : float, default=0
            Percentage regeneration between -95 and 95.
        width : float, default=71,
            Percentage of delayed signal mixed with original between 0 and 100.
        speed : float, default=0.5
            Sweeps per second (in Hz) between 0.1 and 10.
        shape : 'sine' or 'triangle', default='sine'
            Swept wave shape
        phase : float, default=25
            Swept wave percentage phase-shift for multi-channel flange between
            0 and 100. 0 = 100 = same phase on each channel
        interp : 'linear' or 'quadratic', default='linear'
            Digital delay-line interpolation type.

        See Also
        --------
        tremolo
        '''
        if not is_number(delay) or delay < 0 or delay > 30:
            raise ValueError("delay must be a number between 0 and 30.")
        if not is_number(depth) or depth < 0 or depth > 10:
            raise ValueError("depth must be a number between 0 and 10.")
        if not is_number(regen) or regen < -95 or regen > 95:
            raise ValueError("regen must be a number between -95 and 95.")
        if not is_number(width) or width < 0 or width > 100:
            raise ValueError("width must be a number between 0 and 100.")
        if not is_number(speed) or speed < 0.1 or speed > 10:
            raise ValueError("speed must be a number between 0.1 and 10.")
        if shape not in ['sine', 'triangle']:
            raise ValueError("shape must be one of 'sine' or 'triangle'.")
        if not is_number(phase) or phase < 0 or phase > 100:
            raise ValueError("phase must be a number between 0 and 100.")
        if interp not in ['linear', 'quadratic']:
            raise ValueError("interp must be one of 'linear' or 'quadratic'.")

        effect_args = [
            'flanger',
            '{:f}'.format(delay),
            '{:f}'.format(depth),
            '{:f}'.format(regen),
            '{:f}'.format(width),
            '{:f}'.format(speed),
            '{}'.format(shape),
            '{:f}'.format(phase),
            '{}'.format(interp)
        ]

        self.effects.extend(effect_args)
        self.effects_log.append('flanger')

        return self


def gain(self, gain_db=0.0, normalize=True, limiter=False, balance=None):
        '''Apply amplification or attenuation to the audio signal.

        Parameters
        ----------
        gain_db : float, default=0.0
            Gain adjustment in decibels (dB).
        normalize : bool, default=True
            If True, audio is normalized to gain_db relative to full scale.
            If False, simply adjusts the audio power level by gain_db.
        limiter : bool, default=False
            If True, a simple limiter is invoked to prevent clipping.
        balance : str or None, default=None
            Balance gain across channels. Can be one of:
             * None applies no balancing (default)
             * 'e' applies gain to all channels other than that with the
                highest peak level, such that all channels attain the same
                peak level
             * 'B' applies gain to all channels other than that with the
                highest RMS level, such that all channels attain the same
                RMS level
             * 'b' applies gain with clipping protection to all channels other
                than that with the highest RMS level, such that all channels
                attain the same RMS level
            If normalize=True, 'B' and 'b' are equivalent.

        See Also
        --------
        loudness

        '''
        if not is_number(gain_db):
            raise ValueError("gain_db must be a number.")

        if not isinstance(normalize, bool):
            raise ValueError("normalize must be a boolean.")

        if not isinstance(limiter, bool):
            raise ValueError("limiter must be a boolean.")

        if balance not in [None, 'e', 'B', 'b']:
            raise ValueError("balance must be one of None, 'e', 'B', or 'b'.")

        effect_args = ['gain']

        if balance is not None:
            effect_args.append('-{}'.format(balance))

        if normalize:
            effect_args.append('-n')

        if limiter:
            effect_args.append('-l')

        effect_args.append('{:f}'.format(gain_db))
        self.effects.extend(effect_args)
        self.effects_log.append('gain')

        return self

Apply amplification or attenuation to the audio signal .


def loudness(self, gain_db=-10.0, reference_level=65.0):
        '''Loudness control. Similar to the gain effect, but provides
        equalisation for the human auditory system.

        The gain is adjusted by gain_db and the signal is equalised according
        to ISO 226 w.r.t. reference_level.

        Parameters
        ----------
        gain_db : float, default=-10.0
            Loudness adjustment amount (in dB)
        reference_level : float, default=65.0
            Reference level (in dB) according to which the signal is equalized.
            Must be between 50 and 75 (dB)

        See Also
        --------
        gain

        '''
        if not is_number(gain_db):
            raise ValueError('gain_db must be a number.')

        if not is_number(reference_level):
            raise ValueError('reference_level must be a number')

        if reference_level > 75 or reference_level < 50:
            raise ValueError('reference_level must be between 50 and 75')

        effect_args = [
            'loudness',
            '{:f}'.format(gain_db),
            '{:f}'.format(reference_level)
        ]
        self.effects.extend(effect_args)
        self.effects_log.append('loudness')

        return self

Loudness control . Similar to the gain effect but provides equalisation for the human auditory system .


def noiseprof(self, input_filepath, profile_path):
        '''Calculate a profile of the audio for use in noise reduction.
        Running this command does not effect the Transformer effects
        chain. When this function is called, the calculated noise profile
        file is saved to the `profile_path`.

        Parameters
        ----------
        input_filepath : str
            Path to audiofile from which to compute a noise profile.
        profile_path : str
            Path to save the noise profile file.

        See Also
        --------
        noisered

        '''
        if os.path.isdir(profile_path):
            raise ValueError(
                "profile_path {} is a directory.".format(profile_path))

        if os.path.dirname(profile_path) == '' and profile_path != '':
            _abs_profile_path = os.path.join(os.getcwd(), profile_path)
        else:
            _abs_profile_path = profile_path

        if not os.access(os.path.dirname(_abs_profile_path), os.W_OK):
            raise IOError(
                "profile_path {} is not writeable.".format(_abs_profile_path))

        effect_args = ['noiseprof', profile_path]
        self.build(input_filepath, None, extra_args=effect_args)

        return None

def
noiseprof
(
self
,
input_filepath
,
profile_path
)
:
if
os
.
path
.
isdir
(
profile_path
)
:
raise
ValueError
(
"profile_path {} is a directory."
.
format
(
profile_path
)
)
if
os
.
path
.
dirname
(
profile_path
)
==
''
and
profile_path
!=
''
:
_abs_profile_path
=
os
.
path
.
join
(
os
.
getcwd
(
)
,
profile_path
)
else
:
_abs_profile_path
=
profile_path
if
not
os
.
access
(
os
.
path
.
dirname
(
_abs_profile_path
)
,
os
.
W_OK
)
:
raise
IOError
(
"profile_path {} is not writeable."
.
format
(
_abs_profile_path
)
)
effect_args
=
[
'noiseprof'
,
profile_path
]
self
.
build
(
input_filepath
,
,
extra_args
=
effect_args
)
return

Calculate a profile of the audio for use in noise reduction . Running this command does not effect the Transformer effects chain . When this function is called the calculated noise profile file is saved to the profile_path .


def noisered(self, profile_path, amount=0.5):
        '''Reduce noise in the audio signal by profiling and filtering.
        This effect is moderately effective at removing consistent
        background noise such as hiss or hum.

        Parameters
        ----------
        profile_path : str
            Path to a noise profile file.
            This file can be generated using the `noiseprof` effect.
        amount : float, default=0.5
            How much noise should be removed is specified by amount. Should
            be between 0 and 1.  Higher numbers will remove more noise but
            present a greater likelihood  of  removing wanted  components  of
            the  audio  signal.

        See Also
        --------
        noiseprof

        '''

        if not os.path.exists(profile_path):
            raise IOError(
                "profile_path {} does not exist.".format(profile_path))

        if not is_number(amount) or amount < 0 or amount > 1:
            raise ValueError("amount must be a number between 0 and 1.")

        effect_args = [
            'noisered',
            profile_path,
            '{:f}'.format(amount)
        ]
        self.effects.extend(effect_args)
        self.effects_log.append('noisered')

        return self

Reduce noise in the audio signal by profiling and filtering . This effect is moderately effective at removing consistent background noise such as hiss or hum .


def norm(self, db_level=-3.0):
        '''Normalize an audio file to a particular db level.
        This behaves identically to the gain effect with normalize=True.

        Parameters
        ----------
        db_level : float, default=-3.0
            Output volume (db)

        See Also
        --------
        gain, loudness

        '''
        if not is_number(db_level):
            raise ValueError('db_level must be a number.')

        effect_args = [
            'norm',
            '{:f}'.format(db_level)
        ]
        self.effects.extend(effect_args)
        self.effects_log.append('norm')

        return self

def
norm
(
self
,
db_level
=
3.0
)
:
if
not
is_number
(
db_level
)
:
raise
ValueError
(
'db_level must be a number.'
)
effect_args
=
[
'norm'
,
'{:f}'
.
format
(
db_level
)
]
self
.
effects
.
extend
(
effect_args
)
self
.
effects_log
.
append
(
'norm'
)
return
self

Normalize an audio file to a particular db level . This behaves identically to the gain effect with normalize = True .


def oops(self):
        '''Out Of Phase Stereo effect. Mixes stereo to twin-mono where each
        mono channel contains the difference between the left and right stereo
        channels. This is sometimes known as the 'karaoke' effect as it often
        has the effect of removing most or all of the vocals from a recording.

        '''
        effect_args = ['oops']
        self.effects.extend(effect_args)
        self.effects_log.append('oops')

        return self

def oops ( self ) : effect_args = [ 'oops' ] self . effects . extend ( effect_args ) self . effects_log . append ( 'oops' ) return self

Out Of Phase Stereo effect . Mixes stereo to twin - mono where each mono channel contains the difference between the left and right stereo channels . This is sometimes known as the karaoke effect as it often has the effect of removing most or all of the vocals from a recording .


def overdrive(self, gain_db=20.0, colour=20.0):
        '''Apply non-linear distortion.

        Parameters
        ----------
        gain_db : float, default=20
            Controls the amount of distortion (dB).
        colour : float, default=20
            Controls the amount of even harmonic content in the output (dB).

        '''
        if not is_number(gain_db):
            raise ValueError('db_level must be a number.')

        if not is_number(colour):
            raise ValueError('colour must be a number.')

        effect_args = [
            'overdrive',
            '{:f}'.format(gain_db),
            '{:f}'.format(colour)
        ]
        self.effects.extend(effect_args)
        self.effects_log.append('overdrive')

        return self

def
overdrive
(
self
,
gain_db
=
20.0
,
colour
=
20.0
)
:
if
not
is_number
(
gain_db
)
:
raise
ValueError
(
'db_level must be a number.'
)
if
not
is_number
(
colour
)
:
raise
ValueError
(
'colour must be a number.'
)
effect_args
=
[
'overdrive'
,
'{:f}'
.
format
(
gain_db
)
,
'{:f}'
.
format
(
colour
)
]
self
.
effects
.
extend
(
effect_args
)
self
.
effects_log
.
append
(
'overdrive'
)
return
self


def pad(self, start_duration=0.0, end_duration=0.0):
        '''Add silence to the beginning or end of a file.
        Calling this with the default arguments has no effect.

        Parameters
        ----------
        start_duration : float
            Number of seconds of silence to add to beginning.
        end_duration : float
            Number of seconds of silence to add to end.

        See Also
        --------
        delay

        '''
        if not is_number(start_duration) or start_duration < 0:
            raise ValueError("Start duration must be a positive number.")

        if not is_number(end_duration) or end_duration < 0:
            raise ValueError("End duration must be positive.")

        effect_args = [
            'pad',
            '{:f}'.format(start_duration),
            '{:f}'.format(end_duration)
        ]
        self.effects.extend(effect_args)
        self.effects_log.append('pad')

        return self

Add silence to the beginning or end of a file . Calling this with the default arguments has no effect .


def phaser(self, gain_in=0.8, gain_out=0.74, delay=3, decay=0.4, speed=0.5,
               modulation_shape='sinusoidal'):
        '''Apply a phasing effect to the audio.

        Parameters
        ----------
        gain_in : float, default=0.8
            Input volume between 0 and 1
        gain_out: float, default=0.74
            Output volume between 0 and 1
        delay : float, default=3
            Delay in miliseconds between 0 and 5
        decay : float, default=0.4
            Decay relative to gain_in, between 0.1 and 0.5.
        speed : float, default=0.5
            Modulation speed in Hz, between 0.1 and 2
        modulation_shape : str, defaul='sinusoidal'
            Modulation shpae. One of 'sinusoidal' or 'triangular'

        See Also
        --------
        flanger, tremolo
        '''
        if not is_number(gain_in) or gain_in <= 0 or gain_in > 1:
            raise ValueError("gain_in must be a number between 0 and 1.")

        if not is_number(gain_out) or gain_out <= 0 or gain_out > 1:
            raise ValueError("gain_out must be a number between 0 and 1.")

        if not is_number(delay) or delay <= 0 or delay > 5:
            raise ValueError("delay must be a positive number.")

        if not is_number(decay) or decay < 0.1 or decay > 0.5:
            raise ValueError("decay must be a number between 0.1 and 0.5.")

        if not is_number(speed) or speed < 0.1 or speed > 2:
            raise ValueError("speed must be a positive number.")

        if modulation_shape not in ['sinusoidal', 'triangular']:
            raise ValueError(
                "modulation_shape must be one of 'sinusoidal', 'triangular'."
            )

        effect_args = [
            'phaser',
            '{:f}'.format(gain_in),
            '{:f}'.format(gain_out),
            '{:f}'.format(delay),
            '{:f}'.format(decay),
            '{:f}'.format(speed)
        ]

        if modulation_shape == 'sinusoidal':
            effect_args.append('-s')
        elif modulation_shape == 'triangular':
            effect_args.append('-t')

        self.effects.extend(effect_args)
        self.effects_log.append('phaser')

        return self


def pitch(self, n_semitones, quick=False):
        '''Pitch shift the audio without changing the tempo.

        This effect uses the WSOLA algorithm. The audio is chopped up into
        segments which are then shifted in the time domain and overlapped
        (cross-faded) at points where their waveforms are most similar as
        determined by measurement of least squares.

        Parameters
        ----------
        n_semitones : float
            The number of semitones to shift. Can be positive or negative.
        quick : bool, default=False
            If True, this effect will run faster but with lower sound quality.

        See Also
        --------
        bend, speed, tempo

        '''
        if not is_number(n_semitones):
            raise ValueError("n_semitones must be a positive number")

        if n_semitones < -12 or n_semitones > 12:
            logger.warning(
                "Using an extreme pitch shift. "
                "Quality of results will be poor"
            )

        if not isinstance(quick, bool):
            raise ValueError("quick must be a boolean.")

        effect_args = ['pitch']

        if quick:
            effect_args.append('-q')

        effect_args.append('{:f}'.format(n_semitones * 100.))

        self.effects.extend(effect_args)
        self.effects_log.append('pitch')

        return self

def
pitch
(
self
,
n_semitones
,
quick
=
False
)
:
if
not
is_number
(
n_semitones
)
:
raise
ValueError
(
"n_semitones must be a positive number"
)
if
n_semitones
<
12
or
n_semitones
>
12
:
logger
.
warning
(
"Using an extreme pitch shift. "
"Quality of results will be poor"
)
if
not
isinstance
(
quick
,
bool
)
:
raise
ValueError
(
"quick must be a boolean."
)
effect_args
=
[
'pitch'
]
if
quick
:
effect_args
.
append
(
'-q'
)
effect_args
.
append
(
'{:f}'
.
format
(
n_semitones
*
100.
)
)
self
.
effects
.
extend
(
effect_args
)
self
.
effects_log
.
append
(
'pitch'
)
return
self

Pitch shift the audio without changing the tempo .


def remix(self, remix_dictionary=None, num_output_channels=None):
        '''Remix the channels of an audio file.

        Note: volume options are not yet implemented

        Parameters
        ----------
        remix_dictionary : dict or None
            Dictionary mapping output channel to list of input channel(s).
            Empty lists indicate the corresponding output channel should be
            empty. If None, mixes all channels down to a single mono file.
        num_output_channels : int or None
            The number of channels in the output file. If None, the number of
            output channels is equal to the largest key in remix_dictionary.
            If remix_dictionary is None, this variable is ignored.

        Examples
        --------
        Remix a 4-channel input file. The output file will have
        input channel 2 in channel 1, a mixdown of input channels 1 an 3 in
        channel 2, an empty channel 3, and a copy of input channel 4 in
        channel 4.

        >>> import sox
        >>> tfm = sox.Transformer()
        >>> remix_dictionary = {1: [2], 2: [1, 3], 4: [4]}
        >>> tfm.remix(remix_dictionary)

        '''
        if not (isinstance(remix_dictionary, dict) or
                remix_dictionary is None):
            raise ValueError("remix_dictionary must be a dictionary or None.")

        if remix_dictionary is not None:

            if not all([isinstance(i, int) and i > 0 for i
                        in remix_dictionary.keys()]):
                raise ValueError(
                    "remix dictionary must have positive integer keys."
                )

            if not all([isinstance(v, list) for v
                        in remix_dictionary.values()]):
                raise ValueError("remix dictionary values must be lists.")

            for v_list in remix_dictionary.values():
                if not all([isinstance(v, int) and v > 0 for v in v_list]):
                    raise ValueError(
                        "elements of remix dictionary values must "
                        "be positive integers"
                    )

        if not ((isinstance(num_output_channels, int) and
                 num_output_channels > 0) or num_output_channels is None):
            raise ValueError(
                "num_output_channels must be a positive integer or None."
            )

        effect_args = ['remix']
        if remix_dictionary is None:
            effect_args.append('-')
        else:
            if num_output_channels is None:
                num_output_channels = max(remix_dictionary.keys())

            for channel in range(1, num_output_channels + 1):
                if channel in remix_dictionary.keys():
                    out_channel = ','.join(
                        [str(i) for i in remix_dictionary[channel]]
                    )
                else:
                    out_channel = '0'

                effect_args.append(out_channel)

        self.effects.extend(effect_args)
        self.effects_log.append('remix')

        return self


def repeat(self, count=1):
        '''Repeat the entire audio count times.

        Parameters
        ----------
        count : int, default=1
            The number of times to repeat the audio.

        '''
        if not isinstance(count, int) or count < 1:
            raise ValueError("count must be a postive integer.")

        effect_args = ['repeat', '{}'.format(count)]
        self.effects.extend(effect_args)
        self.effects_log.append('repeat')

def
repeat
(
self
,
count
=
1
)
:
if
not
isinstance
(
count
,
int
)
or
count
<
1
:
raise
ValueError
(
"count must be a postive integer."
)
effect_args
=
[
'repeat'
,
'{}'
.
format
(
count
)
]
self
.
effects
.
extend
(
effect_args
)
self
.
effects_log
.
append
(
'repeat'
)


def reverse(self):
        '''Reverse the audio completely
        '''
        effect_args = ['reverse']
        self.effects.extend(effect_args)
        self.effects_log.append('reverse')

        return self

def reverse ( self ) : effect_args = [ 'reverse' ] self . effects . extend ( effect_args ) self . effects_log . append ( 'reverse' ) return self


def silence(self, location=0, silence_threshold=0.1,
                min_silence_duration=0.1, buffer_around_silence=False):
        '''Removes silent regions from an audio file.

        Parameters
        ----------
        location : int, default=0
            Where to remove silence. One of:
             * 0 to remove silence throughout the file (default),
             * 1 to remove silence from the beginning,
             * -1 to remove silence from the end,
        silence_threshold : float, default=0.1
            Silence threshold as percentage of maximum sample amplitude.
            Must be between 0 and 100.
        min_silence_duration : float, default=0.1
            The minimum ammount of time in seconds required for a region to be
            considered non-silent.
        buffer_around_silence : bool, default=False
            If True, leaves a buffer of min_silence_duration around removed
            silent regions.

        See Also
        --------
        vad

        '''
        if location not in [-1, 0, 1]:
            raise ValueError("location must be one of -1, 0, 1.")

        if not is_number(silence_threshold) or silence_threshold < 0:
            raise ValueError(
                "silence_threshold must be a number between 0 and 100"
            )
        elif silence_threshold >= 100:
            raise ValueError(
                "silence_threshold must be a number between 0 and 100"
            )

        if not is_number(min_silence_duration) or min_silence_duration <= 0:
            raise ValueError(
                "min_silence_duration must be a positive number."
            )

        if not isinstance(buffer_around_silence, bool):
            raise ValueError("buffer_around_silence must be a boolean.")

        effect_args = []

        if location == -1:
            effect_args.append('reverse')

        if buffer_around_silence:
            effect_args.extend(['silence', '-l'])
        else:
            effect_args.append('silence')

        effect_args.extend([
            '1',
            '{:f}'.format(min_silence_duration),
            '{:f}%'.format(silence_threshold)
        ])

        if location == 0:
            effect_args.extend([
                '-1',
                '{:f}'.format(min_silence_duration),
                '{:f}%'.format(silence_threshold)
            ])

        if location == -1:
            effect_args.append('reverse')

        self.effects.extend(effect_args)
        self.effects_log.append('silence')

        return self

Removes silent regions from an audio file .


def stat(self, input_filepath, scale=None, rms=False):
        '''Display time and frequency domain statistical information about the
        audio. Audio is passed unmodified through the SoX processing chain.

        Unlike other Transformer methods, this does not modify the transformer
        effects chain. Instead it computes statistics on the output file that
        would be created if the build command were invoked.

        Note: The file is downmixed to mono prior to computation.

        Parameters
        ----------
        input_filepath : str
            Path to input file to compute stats on.
        scale : float or None, default=None
            If not None, scales the input by the given scale factor.
        rms : bool, default=False
            If True, scales all values by the average rms amplitude.

        Returns
        -------
        stat_dict : dict
            Dictionary of statistics.

        See Also
        --------
        stats, power_spectrum, sox.file_info
        '''
        effect_args = ['channels', '1', 'stat']
        if scale is not None:
            if not is_number(scale) or scale <= 0:
                raise ValueError("scale must be a positive number.")
            effect_args.extend(['-s', '{:f}'.format(scale)])

        if rms:
            effect_args.append('-rms')

        _, _, stat_output = self.build(
            input_filepath, None, extra_args=effect_args, return_output=True
        )

        stat_dict = {}
        lines = stat_output.split('\n')
        for line in lines:
            split_line = line.split()
            if len(split_line) == 0:
                continue
            value = split_line[-1]
            key = ' '.join(split_line[:-1])
            stat_dict[key.strip(':')] = value

        return stat_dict

def
stat
(
self
,
input_filepath
,
scale
=
,
rms
=
False
)
:
effect_args
=
[
'channels'
,
'1'
,
'stat'
]
if
scale
is
not
:
if
not
is_number
(
scale
)
or
scale
<=
0
:
raise
ValueError
(
"scale must be a positive number."
)
effect_args
.
extend
(
[
'-s'
,
'{:f}'
.
format
(
scale
)
]
)
if
rms
:
effect_args
.
append
(
'-rms'
)
_
,
_
,
stat_output
=
self
.
build
(
input_filepath
,
,
extra_args
=
effect_args
,
return_output
=
True
)
stat_dict
=
{
}
lines
=
stat_output
.
split
(
'\n'
)
for
line
in
lines
:
split_line
=
line
.
split
(
)
if
len
(
split_line
)
==
0
:
continue
value
=
split_line
[
1
]
key
=
' '
.
join
(
split_line
[
:
1
]
)
stat_dict
[
key
.
strip
(
':'
)
]
=
value
return
stat_dict

Display time and frequency domain statistical information about the audio . Audio is passed unmodified through the SoX processing chain .


def stats(self, input_filepath):
        '''Display time domain statistical information about the audio
        channels. Audio is passed unmodified through the SoX processing chain.
        Statistics are calculated and displayed for each audio channel

        Unlike other Transformer methods, this does not modify the transformer
        effects chain. Instead it computes statistics on the output file that
        would be created if the build command were invoked.

        Note: The file is downmixed to mono prior to computation.

        Parameters
        ----------
        input_filepath : str
            Path to input file to compute stats on.

        Returns
        -------
        stats_dict : dict
            List of frequency (Hz), amplitude pairs.

        See Also
        --------
        stat, sox.file_info
        '''
        effect_args = ['channels', '1', 'stats']

        _, _, stats_output = self.build(
            input_filepath, None, extra_args=effect_args, return_output=True
        )

        stats_dict = {}
        lines = stats_output.split('\n')
        for line in lines:
            split_line = line.split()
            if len(split_line) == 0:
                continue
            value = split_line[-1]
            key = ' '.join(split_line[:-1])
            stats_dict[key] = value

        return stats_dict

def
stats
(
self
,
input_filepath
)
:
effect_args
=
[
'channels'
,
'1'
,
'stats'
]
_
,
_
,
stats_output
=
self
.
build
(
input_filepath
,
,
extra_args
=
effect_args
,
return_output
=
True
)
stats_dict
=
{
}
lines
=
stats_output
.
split
(
'\n'
)
for
line
in
lines
:
split_line
=
line
.
split
(
)
if
len
(
split_line
)
==
0
:
continue
value
=
split_line
[
1
]
key
=
' '
.
join
(
split_line
[
:
1
]
)
stats_dict
[
key
]
=
value
return
stats_dict

Display time domain statistical information about the audio channels . Audio is passed unmodified through the SoX processing chain . Statistics are calculated and displayed for each audio channel


def swap(self):
        '''Swap stereo channels. If the input is not stereo, pairs of channels
        are swapped, and a possible odd last channel passed through.

        E.g., for seven channels, the output order will be 2, 1, 4, 3, 6, 5, 7.

        See Also
        ----------
        remix

        '''
        effect_args = ['swap']
        self.effects.extend(effect_args)
        self.effects_log.append('swap')

        return self

def swap ( self ) : effect_args = [ 'swap' ] self . effects . extend ( effect_args ) self . effects_log . append ( 'swap' ) return self

Swap stereo channels . If the input is not stereo pairs of channels are swapped and a possible odd last channel passed through .