audiogen starts working
a decoupled part that will prepare audio. It has a modular design based on standard python entrypoints. This allows "kinds" to be registered.
This commit is contained in:
parent
b1f597deae
commit
aa71f61425
5 changed files with 181 additions and 1 deletions
42
README.mdwn
Normal file
42
README.mdwn
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
larigira
|
||||||
|
=========
|
||||||
|
|
||||||
|
A radio automation based on MPD. Larigira will sit right to your mpd player and
|
||||||
|
will keep your playlist never empty. It will also manage a db of "events", so
|
||||||
|
that you can schedule shows, play jingles every X minutes, etc.
|
||||||
|
|
||||||
|
Software stack
|
||||||
|
---------------
|
||||||
|
|
||||||
|
* MPD, of course
|
||||||
|
* python2
|
||||||
|
* gevent as an async framework
|
||||||
|
* flask to provide web interface and rpc
|
||||||
|
* celery for task dispatching
|
||||||
|
* ejdb as an embedded database
|
||||||
|
|
||||||
|
Why? (aka design features)
|
||||||
|
-----
|
||||||
|
|
||||||
|
Reinventing a player is a bad idea. MPD provides an eccellent base.
|
||||||
|
|
||||||
|
Separating the player from "action loops" makes it easy to work on this. For
|
||||||
|
example, you can stop larigira for some minutes, and the audio will keep
|
||||||
|
playing. Also, you can replace it.
|
||||||
|
|
||||||
|
The "continous playing" part needs to be separated from the "events" part.
|
||||||
|
`larigira` can be run to perform one, the other, or both the duties.
|
||||||
|
|
||||||
|
The "audio generation" part can be used separately by any script that you like.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Just run `python setup.py install`. It will, of course, also work in a
|
||||||
|
virtualenv.
|
||||||
|
|
||||||
|
The name
|
||||||
|
---------
|
||||||
|
|
||||||
|
> larigira mai la sbaglia
|
||||||
|
|
61
larigira/audiogen.py
Normal file
61
larigira/audiogen.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from pkg_resources import iter_entry_points
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def get_audiogenerator(kind):
|
||||||
|
'''Messes with entrypoints to return an audiogenerator function'''
|
||||||
|
points = tuple(iter_entry_points(group='larigira.audiogenerators',
|
||||||
|
name=kind))
|
||||||
|
if not points:
|
||||||
|
raise ValueError('cant find a generator for ', kind)
|
||||||
|
if len(points) > 1:
|
||||||
|
logging.warning("Found more than one audiogenerator for '%s'" % kind)
|
||||||
|
gen = points[0]
|
||||||
|
return gen.load()
|
||||||
|
|
||||||
|
|
||||||
|
def get_parser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Generate audio and output paths')
|
||||||
|
parser.add_argument('audiospec', metavar='AUDIOSPEC', type=str, nargs=1,
|
||||||
|
help='filename for audiospec, formatted in json')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def read_spec(fname):
|
||||||
|
if fname == '-':
|
||||||
|
return json.load(sys.stdin)
|
||||||
|
with open(fname) as buf:
|
||||||
|
return json.load(buf)
|
||||||
|
|
||||||
|
|
||||||
|
def check_spec(spec):
|
||||||
|
if 'kind' not in spec:
|
||||||
|
yield "Missing field 'kind'"
|
||||||
|
|
||||||
|
|
||||||
|
def generate(spec):
|
||||||
|
gen = get_audiogenerator(spec['kind'])
|
||||||
|
return tuple(gen(spec))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
'''Main function for the "larigira-audiogen" executable'''
|
||||||
|
args = get_parser().parse_args()
|
||||||
|
spec = read_spec(args.audiospec[0])
|
||||||
|
errors = tuple(check_spec(spec))
|
||||||
|
if errors:
|
||||||
|
logging.error("Errors in audiospec")
|
||||||
|
for err in errors:
|
||||||
|
print(err) # TODO: to stderr
|
||||||
|
sys.exit(1)
|
||||||
|
for path in generate(spec):
|
||||||
|
print(path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
48
larigira/audiogen_randomdir.py
Normal file
48
larigira/audiogen_randomdir.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
import random
|
||||||
|
from tempfile import mkstemp
|
||||||
|
import fnmatch
|
||||||
|
|
||||||
|
|
||||||
|
def scan_dir(dirname, extension=None):
|
||||||
|
if extension is None:
|
||||||
|
fnfilter = lambda fnames: fnames
|
||||||
|
else:
|
||||||
|
fnfilter = lambda fnames: fnmatch.filter(fnames, extension)
|
||||||
|
for root, dirnames, filenames in os.walk(dirname):
|
||||||
|
for fname in fnfilter(filenames):
|
||||||
|
yield os.path.join(root, fname)
|
||||||
|
|
||||||
|
|
||||||
|
def generate(spec):
|
||||||
|
'''
|
||||||
|
resolves audiospec-randomdir
|
||||||
|
|
||||||
|
Recognized arguments:
|
||||||
|
- paths [mandatory] list of source paths
|
||||||
|
- howmany [mandatory] number of audio files to pick
|
||||||
|
'''
|
||||||
|
for attr in ('paths', 'howmany'):
|
||||||
|
if attr not in spec:
|
||||||
|
raise ValueError("Malformed audiospec: missing '%s'" % attr)
|
||||||
|
|
||||||
|
found_files = set()
|
||||||
|
for path in spec['paths']:
|
||||||
|
if not os.path.exists(path):
|
||||||
|
logging.warn("Can't find requested path: %s" % path)
|
||||||
|
continue
|
||||||
|
if os.path.isfile(path):
|
||||||
|
found_files.add(path)
|
||||||
|
elif os.path.isdir(path):
|
||||||
|
found_files.update(scan_dir(path))
|
||||||
|
|
||||||
|
picked = random.sample(found_files, int(spec['howmany']))
|
||||||
|
|
||||||
|
for path in picked:
|
||||||
|
tmp = mkstemp(suffix=os.path.splitext(path)[-1],
|
||||||
|
prefix='audiogen-randomdir-')
|
||||||
|
os.close(tmp[0])
|
||||||
|
shutil.copy(path, tmp[1])
|
||||||
|
yield tmp[1]
|
24
larigira/audiogen_static.py
Normal file
24
larigira/audiogen_static.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
from tempfile import mkstemp
|
||||||
|
|
||||||
|
|
||||||
|
def generate(spec):
|
||||||
|
'''
|
||||||
|
resolves audiospec-static
|
||||||
|
|
||||||
|
Recognized argument is "paths" (list of static paths)
|
||||||
|
'''
|
||||||
|
if 'paths' not in spec:
|
||||||
|
raise ValueError("Malformed audiospec: missing 'paths'")
|
||||||
|
|
||||||
|
for path in spec['paths']:
|
||||||
|
if not os.path.exists(path):
|
||||||
|
logging.warn("Can't find requested path: %s" % path)
|
||||||
|
continue
|
||||||
|
tmp = mkstemp(suffix=os.path.splitext(path)[-1],
|
||||||
|
prefix='audiogen-static-')
|
||||||
|
os.close(tmp[0])
|
||||||
|
shutil.copy(path, tmp[1])
|
||||||
|
yield tmp[1]
|
7
setup.py
7
setup.py
|
@ -40,6 +40,11 @@ setup(name='larigira',
|
||||||
cmdclass={'test': PyTest},
|
cmdclass={'test': PyTest},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': ['larigira=larigira.mpc:main']
|
'console_scripts': ['larigira=larigira.mpc:main',
|
||||||
|
'larigira-audiogen=larigira.audiogen:main'],
|
||||||
|
'larigira.audiogenerators': [
|
||||||
|
'static = larigira.audiogen_static:generate',
|
||||||
|
'randomdir = larigira.audiogen_randomdir:generate'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue