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},
|
||||
zip_safe=False,
|
||||
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