Browse Source

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.
boyska 9 years ago
parent
commit
aa71f61425
5 changed files with 181 additions and 1 deletions
  1. 42 0
      README.mdwn
  2. 61 0
      larigira/audiogen.py
  3. 48 0
      larigira/audiogen_randomdir.py
  4. 24 0
      larigira/audiogen_static.py
  5. 6 1
      setup.py

+ 42 - 0
README.mdwn

@@ -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 - 0
larigira/audiogen.py

@@ -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 - 0
larigira/audiogen_randomdir.py

@@ -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 - 0
larigira/audiogen_static.py

@@ -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]

+ 6 - 1
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'
+          ]
       }
       )