def netspeed_by_name(self, hostname):
        """
        Returns NetSpeed name from hostname. Can be Unknown, Dial-up,
        Cable, or Corporate.

        :arg hostname: Hostname (e.g. example.com)
        """
        addr = self._gethostbyname(hostname)
        return self.netspeed_by_addr(addr)

def netspeed_by_name ( self , hostname ) : addr = self . _gethostbyname ( hostname ) return self . netspeed_by_addr ( addr )

Returns NetSpeed name from hostname . Can be Unknown Dial - up Cable or Corporate .


def country_name_by_addr(self, addr):
        """
        Returns full country name for specified IP address.

        :arg addr: IP address (e.g. 203.0.113.30)
        """
        VALID_EDITIONS = (const.COUNTRY_EDITION, const.COUNTRY_EDITION_V6)
        if self._databaseType in VALID_EDITIONS:
            country_id = self.id_by_addr(addr)
            return const.COUNTRY_NAMES[country_id]
        elif self._databaseType in const.CITY_EDITIONS:
            return self.record_by_addr(addr).get('country_name')
        else:
            message = 'Invalid database type, expected Country or City'
            raise GeoIPError(message)

Returns full country name for specified IP address .


def country_name_by_name(self, hostname):
        """
        Returns full country name for specified hostname.

        :arg hostname: Hostname (e.g. example.com)
        """
        addr = self._gethostbyname(hostname)
        return self.country_name_by_addr(addr)

def country_name_by_name ( self , hostname ) : addr = self . _gethostbyname ( hostname ) return self . country_name_by_addr ( addr )

Returns full country name for specified hostname .


def org_by_addr(self, addr):
        """
        Returns Organization, ISP, or ASNum name for given IP address.

        :arg addr: IP address (e.g. 203.0.113.30)
        """
        valid = (const.ORG_EDITION, const.ISP_EDITION,
                 const.ASNUM_EDITION, const.ASNUM_EDITION_V6)
        if self._databaseType not in valid:
            message = 'Invalid database type, expected Org, ISP or ASNum'
            raise GeoIPError(message)

        ipnum = util.ip2long(addr)
        return self._get_org(ipnum)

Returns Organization ISP or ASNum name for given IP address .


def org_by_name(self, hostname):
        """
        Returns Organization, ISP, or ASNum name for given hostname.

        :arg hostname: Hostname (e.g. example.com)
        """
        addr = self._gethostbyname(hostname)
        return self.org_by_addr(addr)

def org_by_name ( self , hostname ) : addr = self . _gethostbyname ( hostname ) return self . org_by_addr ( addr )

Returns Organization ISP or ASNum name for given hostname .


def time_zone_by_country_and_region(country_code, region_code=None):
    """
    Returns time zone from country and region code.

    :arg country_code: Country code
    :arg region_code: Region code
    """
    timezone = country_dict.get(country_code)
    if not timezone:
        return None

    if isinstance(timezone, str):
        return timezone

    return timezone.get(region_code)

def time_zone_by_country_and_region ( country_code , region_code = None ) : timezone = country_dict . get ( country_code ) if not timezone : return None if isinstance ( timezone , str ) : return timezone return timezone . get ( region_code )

Returns time zone from country and region code .


def compress(self, filename):
        """Compress a file, only if needed."""
        compressed_filename = self.get_compressed_filename(filename)
        if not compressed_filename:
            return

        self.do_compress(filename, compressed_filename)

def compress ( self , filename ) : compressed_filename = self . get_compressed_filename ( filename ) if not compressed_filename : return self . do_compress ( filename , compressed_filename )


def get_compressed_filename(self, filename):
        """If the given filename should be compressed, returns the
        compressed filename.

        A file can be compressed if:

        - It is a whitelisted extension
        - The compressed file does not exist
        - The compressed file exists by is older than the file itself

        Otherwise, it returns False.

        """
        if not os.path.splitext(filename)[1][1:] in self.suffixes_to_compress:
            return False

        file_stats = None
        compressed_stats = None
        compressed_filename = '{}.{}'.format(filename, self.suffix)
        try:
            file_stats = os.stat(filename)
            compressed_stats = os.stat(compressed_filename)
        except OSError:  # FileNotFoundError is for Python3 only
            pass

        if file_stats and compressed_stats:
            return (compressed_filename
                    if file_stats.st_mtime > compressed_stats.st_mtime
                    else False)
        else:
            return compressed_filename

def
get_compressed_filename
(
self
,
filename
)
:
if
not
os
.
path
.
splitext
(
filename
)
[
1
]
[
1
:
]
in
self
.
suffixes_to_compress
:
return
False
file_stats
=
compressed_stats
=
compressed_filename
=
'{}.{}'
.
format
(
filename
,
self
.
suffix
)
try
:
file_stats
=
os
.
stat
(
filename
)
compressed_stats
=
os
.
stat
(
compressed_filename
)
except
OSError
:
# FileNotFoundError is for Python3 only
pass
if
file_stats
and
compressed_stats
:
return
(
compressed_filename
if
file_stats
.
st_mtime
>
compressed_stats
.
st_mtime
else
False
)
else
:
return
compressed_filename

If the given filename should be compressed returns the compressed filename .


def copy(src, dst, symlink=False, rellink=False):
    """Copy or symlink the file."""
    func = os.symlink if symlink else shutil.copy2
    if symlink and os.path.lexists(dst):
        os.remove(dst)
    if rellink:  # relative symlink from dst
        func(os.path.relpath(src, os.path.dirname(dst)), dst)
    else:
        func(src, dst)

def
copy
(
src
,
dst
,
symlink
=
False
,
rellink
=
False
)
:
func
=
os
.
symlink
if
symlink
else
shutil
.
copy2
if
symlink
and
os
.
path
.
lexists
(
dst
)
:
os
.
remove
(
dst
)
if
rellink
:
# relative symlink from dst
func
(
os
.
path
.
relpath
(
src
,
os
.
path
.
dirname
(
dst
)
)
,
dst
)
else
:
func
(
src
,
dst
)


def url_from_path(path):
    """Transform path to url, converting backslashes to slashes if needed."""

    if os.sep != '/':
        path = '/'.join(path.split(os.sep))
    return quote(path)

def url_from_path ( path ) : if os . sep != '/' : path = '/' . join ( path . split ( os . sep ) ) return quote ( path )

Transform path to url converting backslashes to slashes if needed .


def read_markdown(filename):
    """Reads markdown file, converts output and fetches title and meta-data for
    further processing.
    """
    global MD
    # Use utf-8-sig codec to remove BOM if it is present. This is only possible
    # this way prior to feeding the text to the markdown parser (which would
    # also default to pure utf-8)
    with open(filename, 'r', encoding='utf-8-sig') as f:
        text = f.read()

    if MD is None:
        MD = Markdown(extensions=['markdown.extensions.meta',
                                  'markdown.extensions.tables'],
                      output_format='html5')
    else:
        MD.reset()
        # When https://github.com/Python-Markdown/markdown/pull/672
        # will be available, this can be removed.
        MD.Meta = {}

    # Mark HTML with Markup to prevent jinja2 autoescaping
    output = {'description': Markup(MD.convert(text))}

    try:
        meta = MD.Meta.copy()
    except AttributeError:
        pass
    else:
        output['meta'] = meta
        try:
            output['title'] = MD.Meta['title'][0]
        except KeyError:
            pass

    return output

Reads markdown file converts output and fetches title and meta - data for further processing .


def load_exif(album):
    """Loads the exif data of all images in an album from cache"""
    if not hasattr(album.gallery, "exifCache"):
        _restore_cache(album.gallery)
    cache = album.gallery.exifCache

    for media in album.medias:
        if media.type == "image":
            key = os.path.join(media.path, media.filename)
            if key in cache:
                media.exif = cache[key]

def load_exif ( album ) : if not hasattr ( album . gallery , "exifCache" ) : _restore_cache ( album . gallery ) cache = album . gallery . exifCache for media in album . medias : if media . type == "image" : key = os . path . join ( media . path , media . filename ) if key in cache : media . exif = cache [ key ]


def _restore_cache(gallery):
    """Restores the exif data cache from the cache file"""
    cachePath = os.path.join(gallery.settings["destination"], ".exif_cache")
    try:
        if os.path.exists(cachePath):
            with open(cachePath, "rb") as cacheFile:
                gallery.exifCache = pickle.load(cacheFile)
                logger.debug("Loaded cache with %d entries", len(gallery.exifCache))
        else:
            gallery.exifCache = {}
    except Exception as e:
        logger.warn("Could not load cache: %s", e)
        gallery.exifCache = {}

def
_restore_cache
(
gallery
)
:
cachePath
=
os
.
path
.
join
(
gallery
.
settings
[
"destination"
]
,
".exif_cache"
)
try
:
if
os
.
path
.
exists
(
cachePath
)
:
with
open
(
cachePath
,
"rb"
)
as
cacheFile
:
gallery
.
exifCache
=
pickle
.
load
(
cacheFile
)
logger
.
debug
(
"Loaded cache with %d entries"
,
len
(
gallery
.
exifCache
)
)
else
:
gallery
.
exifCache
=
{
}
except
Exception
as
e
:
logger
.
warn
(
"Could not load cache: %s"
,
e
)
gallery
.
exifCache
=
{
}


def save_cache(gallery):
    """Stores the exif data of all images in the gallery"""

    if hasattr(gallery, "exifCache"):
        cache = gallery.exifCache
    else:
        cache = gallery.exifCache = {}

    for album in gallery.albums.values():
        for image in album.images:
            cache[os.path.join(image.path, image.filename)] = image.exif

    cachePath = os.path.join(gallery.settings["destination"], ".exif_cache")

    if len(cache) == 0:
        if os.path.exists(cachePath):
            os.remove(cachePath)
        return

    try:
        with open(cachePath, "wb") as cacheFile:
            pickle.dump(cache, cacheFile)
            logger.debug("Stored cache with %d entries", len(gallery.exifCache))
    except Exception as e:
        logger.warn("Could not store cache: %s", e)
        os.remove(cachePath)

def
save_cache
(
gallery
)
:
if
hasattr
(
gallery
,
"exifCache"
)
:
cache
=
gallery
.
exifCache
else
:
cache
=
gallery
.
exifCache
=
{
}
for
album
in
gallery
.
albums
.
values
(
)
:
for
image
in
album
.
images
:
cache
[
os
.
path
.
join
(
image
.
path
,
image
.
filename
)
]
=
image
.
exif
cachePath
=
os
.
path
.
join
(
gallery
.
settings
[
"destination"
]
,
".exif_cache"
)
if
len
(
cache
)
==
0
:
if
os
.
path
.
exists
(
cachePath
)
:
os
.
remove
(
cachePath
)
return
try
:
with
open
(
cachePath
,
"wb"
)
as
cacheFile
:
pickle
.
dump
(
cache
,
cacheFile
)
logger
.
debug
(
"Stored cache with %d entries"
,
len
(
gallery
.
exifCache
)
)
except
Exception
as
e
:
logger
.
warn
(
"Could not store cache: %s"
,
e
)
os
.
remove
(
cachePath
)


def filter_nomedia(album, settings=None):
    """Removes all filtered Media and subdirs from an Album"""
    nomediapath = os.path.join(album.src_path, ".nomedia")

    if os.path.isfile(nomediapath):
        if os.path.getsize(nomediapath) == 0:
            logger.info("Ignoring album '%s' because of present 0-byte "
                        ".nomedia file", album.name)

            # subdirs have been added to the gallery already, remove them
            # there, too
            _remove_albums_with_subdirs(album.gallery.albums, [album.path])
            try:
                os.rmdir(album.dst_path)
            except OSError as e:
                # directory was created and populated with images in a
                # previous run => keep it
                pass

            # cannot set albums => empty subdirs so that no albums are
            # generated
            album.subdirs = []
            album.medias = []

        else:
            with open(nomediapath, "r") as nomediaFile:
                logger.info("Found a .nomedia file in %s, ignoring its "
                            "entries", album.name)
                ignored = nomediaFile.read().split("\n")

                album.medias = [media for media in album.medias
                                if media.src_filename not in ignored]
                album.subdirs = [dirname for dirname in album.subdirs
                                 if dirname not in ignored]

                # subdirs have been added to the gallery already, remove
                # them there, too
                _remove_albums_with_subdirs(album.gallery.albums,
                                            ignored, album.path + os.path.sep)


def build(source, destination, debug, verbose, force, config, theme, title,
          ncpu):
    """Run sigal to process a directory.

    If provided, 'source', 'destination' and 'theme' will override the
    corresponding values from the settings file.

    """
    level = ((debug and logging.DEBUG) or (verbose and logging.INFO) or
             logging.WARNING)
    init_logging(__name__, level=level)
    logger = logging.getLogger(__name__)

    if not os.path.isfile(config):
        logger.error("Settings file not found: %s", config)
        sys.exit(1)

    start_time = time.time()
    settings = read_settings(config)

    for key in ('source', 'destination', 'theme'):
        arg = locals()[key]
        if arg is not None:
            settings[key] = os.path.abspath(arg)
        logger.info("%12s : %s", key.capitalize(), settings[key])

    if not settings['source'] or not os.path.isdir(settings['source']):
        logger.error("Input directory not found: %s", settings['source'])
        sys.exit(1)

    # on windows os.path.relpath raises a ValueError if the two paths are on
    # different drives, in that case we just ignore the exception as the two
    # paths are anyway not relative
    relative_check = True
    try:
        relative_check = os.path.relpath(settings['destination'],
                                         settings['source']).startswith('..')
    except ValueError:
        pass

    if not relative_check:
        logger.error("Output directory should be outside of the input "
                     "directory.")
        sys.exit(1)

    if title:
        settings['title'] = title

    locale.setlocale(locale.LC_ALL, settings['locale'])
    init_plugins(settings)

    gal = Gallery(settings, ncpu=ncpu)
    gal.build(force=force)

    # copy extra files
    for src, dst in settings['files_to_copy']:
        src = os.path.join(settings['source'], src)
        dst = os.path.join(settings['destination'], dst)
        logger.debug('Copy %s to %s', src, dst)
        copy(src, dst, symlink=settings['orig_link'], rellink=settings['rel_link'])

    stats = gal.stats

    def format_stats(_type):
        opt = ["{} {}".format(stats[_type + '_' + subtype], subtype)
               for subtype in ('skipped', 'failed')
               if stats[_type + '_' + subtype] > 0]
        opt = ' ({})'.format(', '.join(opt)) if opt else ''
        return '{} {}s{}'.format(stats[_type], _type, opt)

    print('Done.\nProcessed {} and {} in {:.2f} seconds.'
          .format(format_stats('image'), format_stats('video'),
                  time.time() - start_time))


def serve(destination, port, config):
    """Run a simple web server."""
    if os.path.exists(destination):
        pass
    elif os.path.exists(config):
        settings = read_settings(config)
        destination = settings.get('destination')
        if not os.path.exists(destination):
            sys.stderr.write("The '{}' directory doesn't exist, maybe try "
                             "building first?\n".format(destination))
            sys.exit(1)
    else:
        sys.stderr.write("The {destination} directory doesn't exist "
                         "and the config file ({config}) could not be read.\n"
                         .format(destination=destination, config=config))
        sys.exit(2)

    print('DESTINATION : {}'.format(destination))
    os.chdir(destination)
    Handler = server.SimpleHTTPRequestHandler
    httpd = socketserver.TCPServer(("", port), Handler, False)
    print(" * Running on http://127.0.0.1:{}/".format(port))

    try:
        httpd.allow_reuse_address = True
        httpd.server_bind()
        httpd.server_activate()
        httpd.serve_forever()
    except KeyboardInterrupt:
        print('\nAll done!')


def set_meta(target, keys, overwrite=False):
    """Write metadata keys to .md file.

    TARGET can be a media file or an album directory. KEYS are key/value pairs.

    Ex, to set the title of test.jpg to "My test image":

    sigal set_meta test.jpg title "My test image"
    """

    if not os.path.exists(target):
        sys.stderr.write("The target {} does not exist.\n".format(target))
        sys.exit(1)
    if len(keys) < 2 or len(keys) % 2 > 0:
        sys.stderr.write("Need an even number of arguments.\n")
        sys.exit(1)

    if os.path.isdir(target):
        descfile = os.path.join(target, 'index.md')
    else:
        descfile = os.path.splitext(target)[0] + '.md'
    if os.path.exists(descfile) and not overwrite:
        sys.stderr.write("Description file '{}' already exists. "
                         "Use --overwrite to overwrite it.\n".format(descfile))
        sys.exit(2)

    with open(descfile, "w") as fp:
        for i in range(len(keys) // 2):
            k, v = keys[i * 2:(i + 1) * 2]
            fp.write("{}: {}\n".format(k.capitalize(), v))
    print("{} metadata key(s) written to {}".format(len(keys) // 2, descfile))


def generate_image(source, outname, settings, options=None):
    """Image processor, rotate and resize the image.

    :param source: path to an image
    :param outname: output filename
    :param settings: settings dict
    :param options: dict with PIL options (quality, optimize, progressive)

    """

    logger = logging.getLogger(__name__)

    if settings['use_orig'] or source.endswith('.gif'):
        utils.copy(source, outname, symlink=settings['orig_link'])
        return

    img = _read_image(source)
    original_format = img.format

    if settings['copy_exif_data'] and settings['autorotate_images']:
        logger.warning("The 'autorotate_images' and 'copy_exif_data' settings "
                       "are not compatible because Sigal can't save the "
                       "modified Orientation tag.")

    # Preserve EXIF data
    if settings['copy_exif_data'] and _has_exif_tags(img):
        if options is not None:
            options = deepcopy(options)
        else:
            options = {}
        options['exif'] = img.info['exif']

    # Rotate the img, and catch IOError when PIL fails to read EXIF
    if settings['autorotate_images']:
        try:
            img = Transpose().process(img)
        except (IOError, IndexError):
            pass

    # Resize the image
    if settings['img_processor']:
        try:
            logger.debug('Processor: %s', settings['img_processor'])
            processor_cls = getattr(pilkit.processors,
                                    settings['img_processor'])
        except AttributeError:
            logger.error('Wrong processor name: %s', settings['img_processor'])
            sys.exit()

        width, height = settings['img_size']

        if img.size[0] < img.size[1]:
            # swap target size if image is in portrait mode
            height, width = width, height

        processor = processor_cls(width, height, upscale=False)
        img = processor.process(img)

    # signal.send() does not work here as plugins can modify the image, so we
    # iterate other the receivers to call them with the image.
    for receiver in signals.img_resized.receivers_for(img):
        img = receiver(img, settings=settings)

    outformat = img.format or original_format or 'JPEG'
    logger.debug('Save resized image to %s (%s)', outname, outformat)
    save_image(img, outname, outformat, options=options, autoconvert=True)

Image processor rotate and resize the image .


def generate_thumbnail(source, outname, box, fit=True, options=None,
                       thumb_fit_centering=(0.5, 0.5)):
    """Create a thumbnail image."""

    logger = logging.getLogger(__name__)
    img = _read_image(source)
    original_format = img.format

    if fit:
        img = ImageOps.fit(img, box, PILImage.ANTIALIAS,
                           centering=thumb_fit_centering)
    else:
        img.thumbnail(box, PILImage.ANTIALIAS)

    outformat = img.format or original_format or 'JPEG'
    logger.debug('Save thumnail image: %s (%s)', outname, outformat)
    save_image(img, outname, outformat, options=options, autoconvert=True)

def
generate_thumbnail
(
source
,
outname
,
box
,
fit
=
True
,
options
=
,
thumb_fit_centering
=
(
0.5
,
0.5
)
)
:
logger
=
logging
.
getLogger
(
__name__
)
img
=
_read_image
(
source
)
original_format
=
img
.
format
if
fit
:
img
=
ImageOps
.
fit
(
img
,
box
,
PILImage
.
ANTIALIAS
,
centering
=
thumb_fit_centering
)
else
:
img
.
thumbnail
(
box
,
PILImage
.
ANTIALIAS
)
outformat
=
img
.
format
or
original_format
or
'JPEG'
logger
.
debug
(
'Save thumnail image: %s (%s)'
,
outname
,
outformat
)
save_image
(
img
,
outname
,
outformat
,
options
=
options
,
autoconvert
=
True
)


def get_exif_data(filename):
    """Return a dict with the raw EXIF data."""

    logger = logging.getLogger(__name__)

    img = _read_image(filename)

    try:
        exif = img._getexif() or {}
    except ZeroDivisionError:
        logger.warning('Failed to read EXIF data.')
        return None

    data = {TAGS.get(tag, tag): value for tag, value in exif.items()}

    if 'GPSInfo' in data:
        try:
            data['GPSInfo'] = {GPSTAGS.get(tag, tag): value
                               for tag, value in data['GPSInfo'].items()}
        except AttributeError:
            logger = logging.getLogger(__name__)
            logger.info('Failed to get GPS Info')
            del data['GPSInfo']
    return data

def
get_exif_data
(
filename
)
:
logger
=
logging
.
getLogger
(
__name__
)
img
=
_read_image
(
filename
)
try
:
exif
=
img
.
_getexif
(
)
or
{
}
except
ZeroDivisionError
:
logger
.
warning
(
'Failed to read EXIF data.'
)
return
data
=
{
TAGS
.
get
(
tag
,
tag
)
:
value
for
tag
,
value
in
exif
.
items
(
)
}
if
'GPSInfo'
in
data
:
try
:
data
[
'GPSInfo'
]
=
{
GPSTAGS
.
get
(
tag
,
tag
)
:
value
for
tag
,
value
in
data
[
'GPSInfo'
]
.
items
(
)
}
except
AttributeError
:
logger
=
logging
.
getLogger
(
__name__
)
logger
.
info
(
'Failed to get GPS Info'
)
del
data
[
'GPSInfo'
]
return
data


def get_iptc_data(filename):
    """Return a dict with the raw IPTC data."""

    logger = logging.getLogger(__name__)

    iptc_data = {}
    raw_iptc = {}

    # PILs IptcImagePlugin issues a SyntaxError in certain circumstances
    # with malformed metadata, see PIL/IptcImagePlugin.py", line 71.
    # ( https://github.com/python-pillow/Pillow/blob/9dd0348be2751beb2c617e32ff9985aa2f92ae5f/src/PIL/IptcImagePlugin.py#L71 )
    try:
        img = _read_image(filename)
        raw_iptc = IptcImagePlugin.getiptcinfo(img)
    except SyntaxError:
        logger.info('IPTC Error in %s', filename)

    # IPTC fields are catalogued in:
    # https://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata
    # 2:05 is the IPTC title property
    if raw_iptc and (2, 5) in raw_iptc:
        iptc_data["title"] = raw_iptc[(2, 5)].decode('utf-8', errors='replace')

    # 2:120 is the IPTC description property
    if raw_iptc and (2, 120) in raw_iptc:
        iptc_data["description"] = raw_iptc[(2, 120)].decode('utf-8',
                                                             errors='replace')

    # 2:105 is the IPTC headline property
    if raw_iptc and (2, 105) in raw_iptc:
        iptc_data["headline"] = raw_iptc[(2, 105)].decode('utf-8',
                                                          errors='replace')

    return iptc_data


def get_exif_tags(data, datetime_format='%c'):
    """Make a simplified version with common tags from raw EXIF data."""

    logger = logging.getLogger(__name__)
    simple = {}

    for tag in ('Model', 'Make', 'LensModel'):
        if tag in data:
            if isinstance(data[tag], tuple):
                simple[tag] = data[tag][0].strip()
            else:
                simple[tag] = data[tag].strip()

    if 'FNumber' in data:
        fnumber = data['FNumber']
        try:
            simple['fstop'] = float(fnumber[0]) / fnumber[1]
        except Exception:
            logger.debug('Skipped invalid FNumber: %r', fnumber, exc_info=True)

    if 'FocalLength' in data:
        focal = data['FocalLength']
        try:
            simple['focal'] = round(float(focal[0]) / focal[1])
        except Exception:
            logger.debug('Skipped invalid FocalLength: %r', focal,
                         exc_info=True)

    if 'ExposureTime' in data:
        exptime = data['ExposureTime']
        if isinstance(exptime, tuple):
            try:
                simple['exposure'] = str(fractions.Fraction(exptime[0],
                                                            exptime[1]))
            except ZeroDivisionError:
                logger.info('Invalid ExposureTime: %r', exptime)
        elif isinstance(exptime, int):
            simple['exposure'] = str(exptime)
        else:
            logger.info('Unknown format for ExposureTime: %r', exptime)

    if data.get('ISOSpeedRatings'):
        simple['iso'] = data['ISOSpeedRatings']

    if 'DateTimeOriginal' in data:
        # Remove null bytes at the end if necessary
        date = data['DateTimeOriginal'].rsplit('\x00')[0]

        try:
            simple['dateobj'] = datetime.strptime(date, '%Y:%m:%d %H:%M:%S')
            simple['datetime'] = simple['dateobj'].strftime(datetime_format)
        except (ValueError, TypeError) as e:
            logger.info('Could not parse DateTimeOriginal: %s', e)

    if 'GPSInfo' in data:
        info = data['GPSInfo']
        lat_info = info.get('GPSLatitude')
        lon_info = info.get('GPSLongitude')
        lat_ref_info = info.get('GPSLatitudeRef')
        lon_ref_info = info.get('GPSLongitudeRef')

        if lat_info and lon_info and lat_ref_info and lon_ref_info:
            try:
                lat = dms_to_degrees(lat_info)
                lon = dms_to_degrees(lon_info)
            except (ZeroDivisionError, ValueError, TypeError):
                logger.info('Failed to read GPS info')
            else:
                simple['gps'] = {
                    'lat': - lat if lat_ref_info != 'N' else lat,
                    'lon': - lon if lon_ref_info != 'E' else lon,
                }

    return simple

Make a simplified version with common tags from raw EXIF data .


def create_output_directories(self):
        """Create output directories for thumbnails and original images."""
        check_or_create_dir(self.dst_path)

        if self.medias:
            check_or_create_dir(join(self.dst_path,
                                     self.settings['thumb_dir']))

        if self.medias and self.settings['keep_orig']:
            self.orig_path = join(self.dst_path, self.settings['orig_dir'])
            check_or_create_dir(self.orig_path)

def create_output_directories ( self ) : check_or_create_dir ( self . dst_path ) if self . medias : check_or_create_dir ( join ( self . dst_path , self . settings [ 'thumb_dir' ] ) ) if self . medias and self . settings [ 'keep_orig' ] : self . orig_path = join ( self . dst_path , self . settings [ 'orig_dir' ] ) check_or_create_dir ( self . orig_path )

Create output directories for thumbnails and original images .


def url(self):
        """URL of the album, relative to its parent."""
        url = self.name.encode('utf-8')
        return url_quote(url) + '/' + self.url_ext

def url ( self ) : url = self . name . encode ( 'utf-8' ) return url_quote ( url ) + '/' + self . url_ext

URL of the album relative to its parent .


def thumbnail(self):
        """Path to the thumbnail of the album."""

        if self._thumbnail:
            # stop if it is already set
            return self._thumbnail

        # Test the thumbnail from the Markdown file.
        thumbnail = self.meta.get('thumbnail', [''])[0]

        if thumbnail and isfile(join(self.src_path, thumbnail)):
            self._thumbnail = url_from_path(join(
                self.name, get_thumb(self.settings, thumbnail)))
            self.logger.debug("Thumbnail for %r : %s", self, self._thumbnail)
            return self._thumbnail
        else:
            # find and return the first landscape image
            for f in self.medias:
                ext = splitext(f.filename)[1]
                if ext.lower() in self.settings['img_extensions']:
                    # Use f.size if available as it is quicker (in cache), but
                    # fallback to the size of src_path if dst_path is missing
                    size = f.size
                    if size is None:
                        size = get_size(f.src_path)

                    if size['width'] > size['height']:
                        self._thumbnail = (url_quote(self.name) + '/' +
                                           f.thumbnail)
                        self.logger.debug(
                            "Use 1st landscape image as thumbnail for %r : %s",
                            self, self._thumbnail)
                        return self._thumbnail

            # else simply return the 1st media file
            if not self._thumbnail and self.medias:
                for media in self.medias:
                    if media.thumbnail is not None:
                        self._thumbnail = (url_quote(self.name) + '/' +
                                           media.thumbnail)
                        break
                else:
                    self.logger.warning("No thumbnail found for %r", self)
                    return None

                self.logger.debug("Use the 1st image as thumbnail for %r : %s",
                                  self, self._thumbnail)
                return self._thumbnail

            # use the thumbnail of their sub-directories
            if not self._thumbnail:
                for path, album in self.gallery.get_albums(self.path):
                    if album.thumbnail:
                        self._thumbnail = (url_quote(self.name) + '/' +
                                           album.thumbnail)
                        self.logger.debug(
                            "Using thumbnail from sub-directory for %r : %s",
                            self, self._thumbnail)
                        return self._thumbnail

        self.logger.error('Thumbnail not found for %r', self)
        return None


def zip(self):
        """Make a ZIP archive with all media files and return its path.

        If the ``zip_gallery`` setting is set,it contains the location of a zip
        archive with all original images of the corresponding directory.

        """
        zip_gallery = self.settings['zip_gallery']

        if zip_gallery and len(self) > 0:
            zip_gallery = zip_gallery.format(album=self)
            archive_path = join(self.dst_path, zip_gallery)
            if (self.settings.get('zip_skip_if_exists', False) and
                    isfile(archive_path)):
                self.logger.debug("Archive %s already created, passing",
                                  archive_path)
                return zip_gallery

            archive = zipfile.ZipFile(archive_path, 'w', allowZip64=True)
            attr = ('src_path' if self.settings['zip_media_format'] == 'orig'
                    else 'dst_path')

            for p in self:
                path = getattr(p, attr)
                try:
                    archive.write(path, os.path.split(path)[1])
                except OSError as e:
                    self.logger.warn('Failed to add %s to the ZIP: %s', p, e)

            archive.close()
            self.logger.debug('Created ZIP archive %s', archive_path)
            return zip_gallery

def
zip
(
self
)
:
zip_gallery
=
self
.
settings
[
'zip_gallery'
]
if
zip_gallery
and
len
(
self
)
>
0
:
zip_gallery
=
zip_gallery
.
format
(
album
=
self
)
archive_path
=
join
(
self
.
dst_path
,
zip_gallery
)
if
(
self
.
settings
.
get
(
'zip_skip_if_exists'
,
False
)
and
isfile
(
archive_path
)
)
:
self
.
logger
.
debug
(
"Archive %s already created, passing"
,
archive_path
)
return
zip_gallery
archive
=
zipfile
.
ZipFile
(
archive_path
,
'w'
,
allowZip64
=
True
)
attr
=
(
'src_path'
if
self
.
settings
[
'zip_media_format'
]
==
'orig'
else
'dst_path'
)
for
p
in
self
:
path
=
getattr
(
p
,
attr
)
try
:
archive
.
write
(
path
,
os
.
path
.
split
(
path
)
[
1
]
)
except
OSError
as
e
:
self
.
logger
.
warn
(
'Failed to add %s to the ZIP: %s'
,
p
,
e
)
archive
.
close
(
)
self
.
logger
.
debug
(
'Created ZIP archive %s'
,
archive_path
)
return
zip_gallery

Make a ZIP archive with all media files and return its path .


def get_albums(self, path):
        """Return the list of all sub-directories of path."""

        for name in self.albums[path].subdirs:
            subdir = os.path.normpath(join(path, name))
            yield subdir, self.albums[subdir]
            for subname, album in self.get_albums(subdir):
                yield subname, self.albums[subdir]

def get_albums ( self , path ) : for name in self . albums [ path ] . subdirs : subdir = os . path . normpath ( join ( path , name ) ) yield subdir , self . albums [ subdir ] for subname , album in self . get_albums ( subdir ) : yield subname , self . albums [ subdir ]

Return the list of all sub - directories of path .


def build(self, force=False):
        "Create the image gallery"

        if not self.albums:
            self.logger.warning("No albums found.")
            return

        def log_func(x):
            # 63 is the total length of progressbar, label, percentage, etc
            available_length = get_terminal_size()[0] - 64
            if x and available_length > 10:
                return x.name[:available_length]
            else:
                return ""

        try:
            with progressbar(self.albums.values(), label="Collecting files",
                             item_show_func=log_func, show_eta=False,
                             file=self.progressbar_target) as albums:
                media_list = [f for album in albums
                              for f in self.process_dir(album, force=force)]
        except KeyboardInterrupt:
            sys.exit('Interrupted')

        bar_opt = {'label': "Processing files",
                   'show_pos': True,
                   'file': self.progressbar_target}
        failed_files = []

        if self.pool:
            try:
                with progressbar(length=len(media_list), **bar_opt) as bar:
                    for res in self.pool.imap_unordered(worker, media_list):
                        if res:
                            failed_files.append(res)
                        bar.update(1)
                self.pool.close()
                self.pool.join()
            except KeyboardInterrupt:
                self.pool.terminate()
                sys.exit('Interrupted')
            except pickle.PicklingError:
                self.logger.critical(
                    "Failed to process files with the multiprocessing feature."
                    " This can be caused by some module import or object "
                    "defined in the settings file, which can't be serialized.",
                    exc_info=True)
                sys.exit('Abort')
        else:
            with progressbar(media_list, **bar_opt) as medias:
                for media_item in medias:
                    res = process_file(media_item)
                    if res:
                        failed_files.append(res)

        if failed_files:
            self.remove_files(failed_files)

        if self.settings['write_html']:
            album_writer = AlbumPageWriter(self.settings,
                                           index_title=self.title)
            album_list_writer = AlbumListPageWriter(self.settings,
                                                    index_title=self.title)
            with progressbar(self.albums.values(),
                             label="%16s" % "Writing files",
                             item_show_func=log_func, show_eta=False,
                             file=self.progressbar_target) as albums:
                for album in albums:
                    if album.albums:
                        if album.medias:
                            self.logger.warning(
                                "Album %s contains sub-albums and images. "
                                "Please move images to their own sub-album. "
                                "Images in album %s will not be visible.",
                                album.title, album.title
                            )
                        album_list_writer.write(album)
                    else:
                        album_writer.write(album)
        print('')

        signals.gallery_build.send(self)


def process_dir(self, album, force=False):
        """Process a list of images in a directory."""
        for f in album:
            if isfile(f.dst_path) and not force:
                self.logger.info("%s exists - skipping", f.filename)
                self.stats[f.type + '_skipped'] += 1
            else:
                self.stats[f.type] += 1
                yield (f.type, f.path, f.filename, f.src_path, album.dst_path,
                       self.settings)

def
process_dir
(
self
,
album
,
force
=
False
)
:
for
f
in
album
:
if
isfile
(
f
.
dst_path
)
and
not
force
:
self
.
logger
.
info
(
"%s exists - skipping"
,
f
.
filename
)
self
.
stats
[
f
.
type
+
'_skipped'
]
+=
1
else
:
self
.
stats
[
f
.
type
]
+=
1
yield
(
f
.
type
,
f
.
path
,
f
.
filename
,
f
.
src_path
,
album
.
dst_path
,
self
.
settings
)

Process a list of images in a directory .


def reduce_opacity(im, opacity):
    """Returns an image with reduced opacity."""
    assert opacity >= 0 and opacity <= 1
    if im.mode != 'RGBA':
        im = im.convert('RGBA')
    else:
        im = im.copy()
    alpha = im.split()[3]
    alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
    im.putalpha(alpha)
    return im

def reduce_opacity ( im , opacity ) : assert opacity >= 0 and opacity <= 1 if im . mode != 'RGBA' : im = im . convert ( 'RGBA' ) else : im = im . copy ( ) alpha = im . split ( ) [ 3 ] alpha = ImageEnhance . Brightness ( alpha ) . enhance ( opacity ) im . putalpha ( alpha ) return im


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 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)

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 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 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
=
)
:
config
=
ConfigParser
(
)
config
.
add_section
(
SECTION
)
if
parser
is
:
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 _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 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
:
# pragma: no cover
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
)
:
# pragma: no cover
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
:
# 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 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 .