diff --git a/README.mdwn b/README.rst similarity index 54% rename from README.mdwn rename to README.rst index 2793921..f7abae2 100644 --- a/README.mdwn +++ b/README.rst @@ -1,6 +1,10 @@ +========= larigira ========= +About +------- + 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. @@ -12,31 +16,36 @@ Software stack * 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. +playing. It also means that you can easily replace specific parts of your radio +automation. -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 "continous playing" part is separated from the "events" part. ``larigira`` +can be run to perform one, the other, or both. 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. +Just run ``python setup.py install``. It will, of course, also work in a +virtualenv. Apart from running an MPD server, there is no additional setup. + +You will find some command in your PATH now; they all begin with ``larigira``, +so the usual ```` is a good way to explore them ;) The name --------- -> larigira mai la sbaglia + larigira mai la sbaglia... + + -- https://www.youtube.com/watch?v=K9XJkOSSdEA diff --git a/larigira/audiogen_mpdrandom.py b/larigira/audiogen_mpdrandom.py new file mode 100644 index 0000000..8503cc6 --- /dev/null +++ b/larigira/audiogen_mpdrandom.py @@ -0,0 +1,24 @@ +import logging +log = logging.getLogger('mpdrandom') +import random + +from mpd import MPDClient + + +def generate_by_artist(spec): + '''choose HOWMANY random artists, and for each one choose a random song''' + for attr in ('howmany',): + if attr not in spec: + raise ValueError("Malformed audiospec: missing '%s'" % attr) + + log.info('generating') + c = MPDClient() + c.connect('localhost', 6600) # TODO: read global options somehow + + artists = c.list('artist') + log.debug("got %d artists" % len(artists)) + if not artists: + raise ValueError("no artists in your mpd database") + for _ in xrange(spec['howmany']): + artist = random.choice(artists) + yield random.choice(c.find('artist', artist))['file'] diff --git a/larigira/celeryconfig.py b/larigira/celeryconfig.py deleted file mode 100644 index 78a7d0b..0000000 --- a/larigira/celeryconfig.py +++ /dev/null @@ -1,2 +0,0 @@ -CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' -CELERYD_POOL_RESTARTS = True diff --git a/larigira/eventutils.py b/larigira/eventutils.py index 324e81a..ca486d7 100644 --- a/larigira/eventutils.py +++ b/larigira/eventutils.py @@ -12,11 +12,13 @@ class ParentedLet(gevent.Greenlet): def __init__(self, queue): gevent.Greenlet.__init__(self) self.parent_queue = queue + self.tracker = None # set this to recognize easily def parent_msg(self, kind, *args): return { 'emitter': self, 'class': self.__class__.__name__, + 'tracker': self.tracker, 'kind': kind, 'args': args } diff --git a/larigira/mpc.py b/larigira/mpc.py index 0bb1a80..26b7536 100644 --- a/larigira/mpc.py +++ b/larigira/mpc.py @@ -1,10 +1,12 @@ from __future__ import print_function from gevent import monkey monkey.patch_all(subprocess=True) +import os import logging +FORMAT = '%(asctime)s|%(levelname)s[%(name)s:%(lineno)d] %(message)s' logging.basicConfig(level=logging.INFO, - format='%(asctime)s %(message)s', + format=FORMAT, datefmt='%H:%M:%S') import signal @@ -13,9 +15,13 @@ from gevent.queue import Queue from gevent.wsgi import WSGIServer from mpd import MPDClient -from eventutils import ParentedLet, CeleryTask, Timer -from task import create as create_continous +from eventutils import ParentedLet, Timer import rpc +from audiogen import generate + +CONTINOUS_AUDIODESC = dict(kind='mpd', howmany=1) +MPD_HOST = os.getenv('MPD_HOST', 'localhost') +MPD_PORT = int(os.getenv('MPD_PORT', '6600')) class MpcWatcher(ParentedLet): @@ -24,13 +30,12 @@ class MpcWatcher(ParentedLet): if client is None: self.client = MPDClient() # TODO: use config values - self.client.connect("localhost", 6600) + self.client.connect(MPD_HOST, MPD_PORT) else: self.client = client # assume it is already connected def do_business(self): while True: - # status = check_output(['mpc', 'idle']).decode('utf-8').strip() status = self.client.idle()[0] logging.info(status) yield ('mpc', status) @@ -50,8 +55,9 @@ class Player(gevent.Greenlet): if(len(songs) >= self.min_playlist_length): return logging.info('need to add new songs') - CeleryTask(create_continous, self.q).start() - CeleryTask(create_continous, self.q).start() + picker = gevent.Greenlet(generate, CONTINOUS_AUDIODESC) + picker.link_value(lambda g: mpd_client.add(next(g.value).strip())) + picker.start() def _run(self): MpcWatcher(self.q, client=None).start() @@ -67,8 +73,8 @@ class Player(gevent.Greenlet): logging.info('CLOCK') if kind == 'timer' or (kind == 'mpc' and args[0] == 'playlist'): gevent.Greenlet.spawn(self.check_playlist) - elif kind == 'celery': - logging.info("celery: %s" % str(args)) + elif value['tracker'] == 'mpd_generate': + logging.info("generated! %s" % tuple(args[0])) else: logging.warning("Unknown message: %s" % str(value)) logging.info(str(value)) diff --git a/larigira/task.py b/larigira/task.py deleted file mode 100644 index 456b0e7..0000000 --- a/larigira/task.py +++ /dev/null @@ -1,41 +0,0 @@ -import time -import logging -import random -logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s %(message)s', - datefmt='%H:%M:%S') - -from celery import Celery - -celery = Celery('hello', backend='redis://localhost', - broker='redis://localhost:6379/0') - - -@celery.task(name='create_continous') -def create(): - sec = random.uniform(2, 5) - time.sleep(sec) - logging.info('hello world') - return 'slept! %.2f' % sec - -if __name__ == '__main__': - celery.control.broadcast('pool_restart', - arguments={'reload': True}) - res = [] - N = 14 - - def callback(*args, **kwargs): - print(args) - print(kwargs) - print('---') - - for i in xrange(N): - print('append', i) - res.append(create.apply_async(expires=2)) - - for i in xrange(N): - logging.info('wait %d' % i) - val = res[i].get() - logging.info('got %s' % str(val)) - - time.sleep(30) diff --git a/setup.py b/setup.py index 9c89839..45cd458 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,15 @@ import sys +import os from setuptools import setup from setuptools.command.test import test as TestCommand +def read(fname): + with open(os.path.join(os.path.dirname(__file__), fname)) as buf: + return buf.read() + + class PyTest(TestCommand): user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] @@ -25,6 +31,7 @@ class PyTest(TestCommand): setup(name='larigira', version='0.1', description='A radio automation based on MPD', + long_description=read('README.rst'), author='boyska', author_email='piuttosto@logorroici.org', license='AGPL', @@ -32,9 +39,7 @@ setup(name='larigira', install_requires=[ 'gevent', 'flask', - 'python-mpd2', - 'redis', - 'celery' + 'python-mpd2' ], tests_require=['pytest'], cmdclass={'test': PyTest}, @@ -43,6 +48,7 @@ setup(name='larigira', 'console_scripts': ['larigira=larigira.mpc:main', 'larigira-audiogen=larigira.audiogen:main'], 'larigira.audiogenerators': [ + 'mpd = larigira.audiogen_mpdrandom:generate_by_artist', 'static = larigira.audiogen_static:generate', 'randomdir = larigira.audiogen_randomdir:generate' ]