No more celery, audiogen used for continous, too
readme is now rst, and included in setup.py
This commit is contained in:
parent
aa71f61425
commit
241eb30c7c
7 changed files with 67 additions and 63 deletions
|
@ -1,6 +1,10 @@
|
||||||
|
=========
|
||||||
larigira
|
larigira
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
About
|
||||||
|
-------
|
||||||
|
|
||||||
A radio automation based on MPD. Larigira will sit right to your mpd player and
|
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
|
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.
|
that you can schedule shows, play jingles every X minutes, etc.
|
||||||
|
@ -12,31 +16,36 @@ Software stack
|
||||||
* python2
|
* python2
|
||||||
* gevent as an async framework
|
* gevent as an async framework
|
||||||
* flask to provide web interface and rpc
|
* flask to provide web interface and rpc
|
||||||
* celery for task dispatching
|
|
||||||
* ejdb as an embedded database
|
* ejdb as an embedded database
|
||||||
|
|
||||||
Why? (aka design features)
|
Why? (aka design features)
|
||||||
-----
|
--------------------------
|
||||||
|
|
||||||
Reinventing a player is a bad idea. MPD provides an eccellent base.
|
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
|
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
|
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.
|
The "continous playing" part is separated from the "events" part. ``larigira``
|
||||||
`larigira` can be run to perform one, the other, or both the duties.
|
can be run to perform one, the other, or both.
|
||||||
|
|
||||||
The "audio generation" part can be used separately by any script that you like.
|
The "audio generation" part can be used separately by any script that you like.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Just run `python setup.py install`. It will, of course, also work in a
|
Just run ``python setup.py install``. It will, of course, also work in a
|
||||||
virtualenv.
|
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 ``<TAB><TAB>`` is a good way to explore them ;)
|
||||||
|
|
||||||
The name
|
The name
|
||||||
---------
|
---------
|
||||||
|
|
||||||
> larigira mai la sbaglia
|
larigira mai la sbaglia...
|
||||||
|
|
||||||
|
-- https://www.youtube.com/watch?v=K9XJkOSSdEA
|
||||||
|
|
24
larigira/audiogen_mpdrandom.py
Normal file
24
larigira/audiogen_mpdrandom.py
Normal file
|
@ -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']
|
|
@ -1,2 +0,0 @@
|
||||||
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
|
|
||||||
CELERYD_POOL_RESTARTS = True
|
|
|
@ -12,11 +12,13 @@ class ParentedLet(gevent.Greenlet):
|
||||||
def __init__(self, queue):
|
def __init__(self, queue):
|
||||||
gevent.Greenlet.__init__(self)
|
gevent.Greenlet.__init__(self)
|
||||||
self.parent_queue = queue
|
self.parent_queue = queue
|
||||||
|
self.tracker = None # set this to recognize easily
|
||||||
|
|
||||||
def parent_msg(self, kind, *args):
|
def parent_msg(self, kind, *args):
|
||||||
return {
|
return {
|
||||||
'emitter': self,
|
'emitter': self,
|
||||||
'class': self.__class__.__name__,
|
'class': self.__class__.__name__,
|
||||||
|
'tracker': self.tracker,
|
||||||
'kind': kind,
|
'kind': kind,
|
||||||
'args': args
|
'args': args
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from gevent import monkey
|
from gevent import monkey
|
||||||
monkey.patch_all(subprocess=True)
|
monkey.patch_all(subprocess=True)
|
||||||
|
import os
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
FORMAT = '%(asctime)s|%(levelname)s[%(name)s:%(lineno)d] %(message)s'
|
||||||
logging.basicConfig(level=logging.INFO,
|
logging.basicConfig(level=logging.INFO,
|
||||||
format='%(asctime)s %(message)s',
|
format=FORMAT,
|
||||||
datefmt='%H:%M:%S')
|
datefmt='%H:%M:%S')
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
|
@ -13,9 +15,13 @@ from gevent.queue import Queue
|
||||||
from gevent.wsgi import WSGIServer
|
from gevent.wsgi import WSGIServer
|
||||||
from mpd import MPDClient
|
from mpd import MPDClient
|
||||||
|
|
||||||
from eventutils import ParentedLet, CeleryTask, Timer
|
from eventutils import ParentedLet, Timer
|
||||||
from task import create as create_continous
|
|
||||||
import rpc
|
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):
|
class MpcWatcher(ParentedLet):
|
||||||
|
@ -24,13 +30,12 @@ class MpcWatcher(ParentedLet):
|
||||||
if client is None:
|
if client is None:
|
||||||
self.client = MPDClient()
|
self.client = MPDClient()
|
||||||
# TODO: use config values
|
# TODO: use config values
|
||||||
self.client.connect("localhost", 6600)
|
self.client.connect(MPD_HOST, MPD_PORT)
|
||||||
else:
|
else:
|
||||||
self.client = client # assume it is already connected
|
self.client = client # assume it is already connected
|
||||||
|
|
||||||
def do_business(self):
|
def do_business(self):
|
||||||
while True:
|
while True:
|
||||||
# status = check_output(['mpc', 'idle']).decode('utf-8').strip()
|
|
||||||
status = self.client.idle()[0]
|
status = self.client.idle()[0]
|
||||||
logging.info(status)
|
logging.info(status)
|
||||||
yield ('mpc', status)
|
yield ('mpc', status)
|
||||||
|
@ -50,8 +55,9 @@ class Player(gevent.Greenlet):
|
||||||
if(len(songs) >= self.min_playlist_length):
|
if(len(songs) >= self.min_playlist_length):
|
||||||
return
|
return
|
||||||
logging.info('need to add new songs')
|
logging.info('need to add new songs')
|
||||||
CeleryTask(create_continous, self.q).start()
|
picker = gevent.Greenlet(generate, CONTINOUS_AUDIODESC)
|
||||||
CeleryTask(create_continous, self.q).start()
|
picker.link_value(lambda g: mpd_client.add(next(g.value).strip()))
|
||||||
|
picker.start()
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
MpcWatcher(self.q, client=None).start()
|
MpcWatcher(self.q, client=None).start()
|
||||||
|
@ -67,8 +73,8 @@ class Player(gevent.Greenlet):
|
||||||
logging.info('CLOCK')
|
logging.info('CLOCK')
|
||||||
if kind == 'timer' or (kind == 'mpc' and args[0] == 'playlist'):
|
if kind == 'timer' or (kind == 'mpc' and args[0] == 'playlist'):
|
||||||
gevent.Greenlet.spawn(self.check_playlist)
|
gevent.Greenlet.spawn(self.check_playlist)
|
||||||
elif kind == 'celery':
|
elif value['tracker'] == 'mpd_generate':
|
||||||
logging.info("celery: %s" % str(args))
|
logging.info("generated! %s" % tuple(args[0]))
|
||||||
else:
|
else:
|
||||||
logging.warning("Unknown message: %s" % str(value))
|
logging.warning("Unknown message: %s" % str(value))
|
||||||
logging.info(str(value))
|
logging.info(str(value))
|
||||||
|
|
|
@ -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)
|
|
12
setup.py
12
setup.py
|
@ -1,9 +1,15 @@
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
from setuptools.command.test import test as TestCommand
|
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):
|
class PyTest(TestCommand):
|
||||||
user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
|
user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
|
||||||
|
|
||||||
|
@ -25,6 +31,7 @@ class PyTest(TestCommand):
|
||||||
setup(name='larigira',
|
setup(name='larigira',
|
||||||
version='0.1',
|
version='0.1',
|
||||||
description='A radio automation based on MPD',
|
description='A radio automation based on MPD',
|
||||||
|
long_description=read('README.rst'),
|
||||||
author='boyska',
|
author='boyska',
|
||||||
author_email='piuttosto@logorroici.org',
|
author_email='piuttosto@logorroici.org',
|
||||||
license='AGPL',
|
license='AGPL',
|
||||||
|
@ -32,9 +39,7 @@ setup(name='larigira',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'gevent',
|
'gevent',
|
||||||
'flask',
|
'flask',
|
||||||
'python-mpd2',
|
'python-mpd2'
|
||||||
'redis',
|
|
||||||
'celery'
|
|
||||||
],
|
],
|
||||||
tests_require=['pytest'],
|
tests_require=['pytest'],
|
||||||
cmdclass={'test': PyTest},
|
cmdclass={'test': PyTest},
|
||||||
|
@ -43,6 +48,7 @@ setup(name='larigira',
|
||||||
'console_scripts': ['larigira=larigira.mpc:main',
|
'console_scripts': ['larigira=larigira.mpc:main',
|
||||||
'larigira-audiogen=larigira.audiogen:main'],
|
'larigira-audiogen=larigira.audiogen:main'],
|
||||||
'larigira.audiogenerators': [
|
'larigira.audiogenerators': [
|
||||||
|
'mpd = larigira.audiogen_mpdrandom:generate_by_artist',
|
||||||
'static = larigira.audiogen_static:generate',
|
'static = larigira.audiogen_static:generate',
|
||||||
'randomdir = larigira.audiogen_randomdir:generate'
|
'randomdir = larigira.audiogen_randomdir:generate'
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue