Initial deploy

This commit is contained in:
Fergal Moran
2015-10-05 20:14:33 +01:00
parent 3ad11b011d
commit 7e6db3bf61
10 changed files with 177 additions and 204 deletions

2
.gitignore vendored
View File

@@ -91,3 +91,5 @@ docs/_build/
# PyBuilder # PyBuilder
target/ target/
config.ini

View File

@@ -1,24 +1,24 @@
FROM ubuntu:wily FROM ubuntu:latest
MAINTAINER Fergal Moran "Ferg@lMoran.me" MAINTAINER Fergal Moran <Ferg@lMoran.me>
ENV DEBIAN_FRONTEND noninteractive
RUN mkdir /code RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get -qq -y update && \ RUN apt-get install -y python-setuptools python-pip git pkg-config libshout3 libshout3-dev python-dev
apt-get -qq -y install icecast2 python-setuptools python-pip pkg-config git \
libcurl4-openssl-dev libshout3 libshout3-dev && \
apt-get clean
RUN easy_install supervisor && \
easy_install supervisor-stdout
RUN mkdir /code/
WORKDIR /code WORKDIR /code
ADD requirements.txt /code/ ADD requirements.txt /code/
ADD server.py /code/
ADD ice_relay.py /code/
ADD static /code/static/
ADD templates /code/templates/
ADD dss.radio.conf /code/
ADD icecast2/icecast.xml /etc/icecast2/ # Install tornado
ADD default/icecast2 /etc/default/
ADD supervisord.conf /etc/supervisord.conf
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
RUN pip install git+https://github.com/fergalmoran/python-shout.git#python-shout --upgrade
EXPOSE 8888
CMD ["python", "server.py"]

View File

@@ -1,19 +0,0 @@
# Defaults for icecast2 initscript
# sourced by /etc/init.d/icecast2
# installed at /etc/default/icecast2 by the maintainer scripts
#
# This is a POSIX shell fragment
#
# Full path to the server configuration file
CONFIGFILE="/etc/icecast2/icecast.xml"
# Name or ID of the user and group the daemon should run under
USERID=icecast2
GROUPID=icecast
# Edit /etc/icecast2/icecast.xml and change at least the passwords.
# Change this to true when done to enable the init.d script
ENABLE=true

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env python
import shout
class IceClient(object):
def __init__(self, host, port, user, password, mount, format, protocol):
self.s = shout.Shout()
print "Using libshout version {}".format(shout.version())
self.s.host = host
self.s.port = port
self.s.user = user
self.s.password = password
self.s.mount = mount
self.s.format = format
self.s.protocol = protocol
self.s.open()
def stop(self):
self.s.close()
"""
if __name__ == '__main__':
streamer = IceClient(
host='niles',
port=8999,
user='source',
password='hackme',
mount="/pyshout",
format='mp3',
protocol='http'
)
streamer.play_audio(['/home/fergalm/Dropbox/BT_The_Moment_of_Truth.mp3'])
"""

View File

@@ -1,61 +1,155 @@
import logging
from threading import Thread from threading import Thread
import time import time
from deefuzzer import Player import urllib
import shout
import urllib2
import requests
BUF_LEN = 4096 BUF_LEN = 4096
class IceRelay(Thread): class IceRelay(Thread):
def __init__(self, client, title='DeepSouthSounds Radio'): server_ping = False
def __init__(self, options, mountpoint='dss', title='DeepSouthSounds Radio'):
super(IceRelay, self).__init__() super(IceRelay, self).__init__()
self.title = title self.title = title
self.s = client
self._ended = True self._ended = True
self.player = Player("icecast")
self.isOpen = True self.isOpen = True
self.audio_queue = [] self.audio_queue = []
self.audio_index = 0 self.audio_index = 0
self.default_queue = [ self.channelIsOpen = False
'https://dsscdn.blob.core.windows.net/mixes/7568d3a4-9a9f-4f0f-a900-f84231c26c47.mp3'
]
def stop(self): self.options = options
self.channel = shout.Shout()
self.channel.mount = '/' + mountpoint
self.api_host = options['api_host']
self.channel.url = 'http://deepsouthsounds.com/'
self.channel.name = title
self.channel.genre = 'Deep House Music'
self.channel.description = 'Deep sounds from the deep south'
self.channel.format = options['ice_format']
self.channel.host = options['ice_host']
self.channel.port = int(options['ice_port'])
self.channel.user = options['ice_user']
self.channel.password = options['ice_password']
self.channel.public = 1
if self.channel.format == 'mp3':
self.channel.audio_info = {
'bitrate': str(320),
'samplerate': str(48000),
'channels': str(2),
}
self.server_url = 'http://' + self.channel.host + ':' + str(self.channel.port) + self.channel.mount
print(self.server_url)
def channel_open(self):
if self.channelIsOpen:
return True
try:
self.channel.open()
self.channelIsOpen = True
return True
except Exception as ex:
logging.error('channel could not be opened: {}'.format(ex))
return False
def channel_close(self):
self.channelIsOpen = False
self._ended = True self._ended = True
try:
self.channel.close()
logging.info('channel closed')
except Exception as ex:
logging.error('channel could not be closed: {}'.format(ex))
def ping_server(self):
log = True
while not self.server_ping:
try:
server = urllib.urlopen(self.server_url)
self.server_ping = True
logging.info('Channel available.')
except:
time.sleep(1)
if log:
logging.error('Could not connect the channel. Waiting for channel to become available.')
log = False
def default_queue(self):
try:
r = requests.get('http://{}/mix/?random=True&limit=1'.format(self.api_host)) \
.json().get('results')[0].get('slug')
r = requests.get('http://{}/mix/{}/stream_url'.format(self.api_host, r))
url = r.json()['url']
return [
url
]
except Exception as ex:
logging.error(ex)
return [
'https://dsscdn.blob.core.windows.net/mixes/52df41af-5f81-4f00-a9a8-9ffb5dc3185f.mp3'
]
def set_audio_queue(self, queue): def set_audio_queue(self, queue):
self.audio_queue = queue self.audio_queue = queue
self._ended = True
def get_next_play_item(self): def get_next_play_item(self):
print "Finding next item" try:
self._ended = False logging.debug("Finding next item")
# get random item from DSS api # get random item from DSS api
if len(self.audio_queue) > self.audio_index: if len(self.audio_queue) > self.audio_index:
item = self.audio_queue[self.audio_index] item = self.audio_queue[self.audio_index]
else: else:
item = self.default_queue[0] item = self.default_queue()[0]
self.player.set_media(item) logging.debug("Playing: {}".format(item))
print "Playing: {}".format(item) self.stream = self.file_read_remote(item)
return self.player.file_read_remote()
def close_channel(self): self._ended = False
self.isOpen = False return True
except Exception as ex:
logging.error('Error getting next play item: {}'.format(ex))
return False
def run(self): def run(self):
self.ping_server()
while True: while True:
now_playing = self.get_next_play_item() now_playing = self.get_next_play_item()
if now_playing is not None: if now_playing is not None:
for chunk in now_playing: for self.chunk in self.stream:
try: try:
self.s.s.send(chunk) self.channel.send(self.chunk)
self.s.s.sync() self.channel.sync()
except Exception as ex: except Exception as ex:
print ("Error sending chunk: {0}".format(ex)) logging.error("Error sending chunk: {0}".format(ex))
self.close_channel() self.channel_close()
if self._ended: if self._ended:
break break
else: else:
print("No audio, waiting") logging.debug("No audio, waiting")
time.sleep(5) time.sleep(5)
print "Outta here........"
def file_read_remote(self, item):
"""Read remote file and stream data through a generator."""
main_buffer_size = 0x10000
m = urllib2.urlopen(item)
while True:
__main_chunk = m.read(main_buffer_size)
if not __main_chunk:
break
yield __main_chunk
m.close()

View File

@@ -1,51 +0,0 @@
<icecast>
<location>Cork Like</location>
<admin>icemaster@deepsouthsounds.com</admin>
<limits>
<clients>100</clients>
<sources>2</sources>
<threadpool>5</threadpool>
<queue-size>524288</queue-size>
<client-timeout>30</client-timeout>
<header-timeout>15</header-timeout>
<source-timeout>10</source-timeout>
<burst-on-connect>1</burst-on-connect>
<burst-size>65535</burst-size>
</limits>
<authentication>
<source-password>RDzNlgqmj67vk</source-password>
<relay-password>9PmUbI1mLne9o</relay-password>
<admin-user>admin</admin-user>
<admin-password>CrVuP5evoJZ0.</admin-password>
</authentication>
<hostname>radio.deepsouthsounds.com</hostname>
<listen-socket>
<port>8351</port>
<shoutcast-mount>/dss</shoutcast-mount>
</listen-socket>
<fileserve>1</fileserve>
<paths>
<basedir>/usr/share/icecast2</basedir>
<logdir>/var/log/icecast2</logdir>
<webroot>/usr/share/icecast2/web</webroot>
<adminroot>/usr/share/icecast2/admin</adminroot>
<alias source="/" destination="/status.xsl"/>
</paths>
<logging>
<accesslog>access.log</accesslog>
<errorlog>error.log</errorlog>
<loglevel>3</loglevel>
<logsize>10000</logsize>
</logging>
<security>
<chroot>0</chroot>
</security>
</icecast>

View File

@@ -1,7 +1,5 @@
twisted
cython cython
requests requests
tornado==4.2.1 tornado==4.2.1
python-shout==0.2.1
git+https://github.com/fergalmoran/DeeFuzzer.git
git+https://github.com/fergalmoran/python-shout.git#python-shout git+https://github.com/fergalmoran/python-shout.git#python-shout

View File

@@ -1,28 +1,16 @@
#!/usr/bin/env python
import logging import logging
import os
from shout import ShoutException import signal
import time import time
import tornado import tornado
import tornado.ioloop import tornado.ioloop
import tornado.web import tornado.web
import tornado.escape import tornado.escape
from tornado.options import define, options, parse_command_line from tornado.options import define, options, parse_command_line
import os
import signal
from ice_client import IceClient
from ice_relay import IceRelay from ice_relay import IceRelay
define("port", default=8888, help="run on the given port", type=int) is_closing = False
define("debug", default=True, help="run in debug mode")
define("ice_host", default='localhost', help="Icecast server host")
define("ice_port", default=8999, help="Icecast server port")
define("ice_user", default='source', help="Icecast user")
define("ice_password", default='hackme', help="Icecast password")
define("ice_mount", default='/mp3', help="Default icecast mount point")
define("ice_format", default='mp3', help="Format of the icecast server (mp3, vorbis, flac)")
define("ice_protocol", default='http', help="Protocol (currently only http)")
class MainHandler(tornado.web.RequestHandler): class MainHandler(tornado.web.RequestHandler):
@@ -37,7 +25,6 @@ class PlayAudioHandler(tornado.web.RequestHandler):
in_file = data.get('audio_file') in_file = data.get('audio_file')
if in_file is not None: if in_file is not None:
relay.set_audio_queue([in_file]) relay.set_audio_queue([in_file])
relay.stop()
time.sleep(10) time.sleep(10)
except Exception, ex: except Exception, ex:
raise tornado.web.HTTPError(500, ex.message) raise tornado.web.HTTPError(500, ex.message)
@@ -46,12 +33,10 @@ class PlayAudioHandler(tornado.web.RequestHandler):
class StopAudioHandler(tornado.web.RequestHandler): class StopAudioHandler(tornado.web.RequestHandler):
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
try: try:
relay.stop() relay.channel_close()
except Exception, ex: except Exception, ex:
raise tornado.web.HTTPError(500, ex.message) raise tornado.web.HTTPError(500, ex.message)
is_closing = False
def signal_handler(signum, frame): def signal_handler(signum, frame):
global is_closing global is_closing
@@ -63,14 +48,35 @@ def try_exit():
global is_closing global is_closing
if is_closing: if is_closing:
# clean up here # clean up here
streamer.stop() relay.channel_close()
relay.stop()
tornado.ioloop.IOLoop.instance().stop() tornado.ioloop.IOLoop.instance().stop()
logging.info('exit success') logging.info('exit success')
define("port", default=8888, help="run on the given port", type=int)
define("debug", default=True, help="run in debug mode")
define("ice_host", default='localhost', help="Icecast server host")
define("ice_port", default=8000, help="Icecast server port")
define("ice_user", default='source', help="Icecast user")
define("ice_password", default='hackme', help="Icecast password")
define("ice_mount", default='/mp3', help="Default icecast mount point")
define("ice_format", default='mp3', help="Format of the icecast server (mp3, vorbis, flac)")
define("ice_protocol", default='http', help="Protocol (currently only http)")
define("api_host", default='api.deepsouthsounds.com', help="API Host for serving audio")
#tornado.options.parse_command_line()
tornado.options.parse_config_file("dss.radio.conf")
relay = IceRelay(options=options)
def main(): def main():
if relay.channel_open():
relay.start()
else:
logging.error("IceCast relay failed to start")
exit()
app = tornado.web.Application( app = tornado.web.Application(
[ [
(r"/", MainHandler), (r"/", MainHandler),
@@ -81,30 +87,13 @@ def main():
template_path=os.path.join(os.path.dirname(__file__), "templates"), template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"), static_path=os.path.join(os.path.dirname(__file__), "static"),
# xsrf_cookies=True, # xsrf_cookies=True,
debug=options.debug debug=options['debug']
) )
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
app.listen(options.port) app.listen(options['port'])
tornado.ioloop.PeriodicCallback(try_exit, 100).start() tornado.ioloop.PeriodicCallback(try_exit, 100).start()
tornado.ioloop.IOLoop.current().start() tornado.ioloop.IOLoop.current().start()
parse_command_line()
try:
streamer = IceClient(
host=options['ice_host'],
port=options['ice_port'],
user=options['ice_user'],
password=options['ice_password'],
mount=options['ice_mount'],
format=options['ice_format'],
protocol=options['ice_protocol']
)
relay = IceRelay(client=streamer)
relay.start()
except ShoutException as ex:
logging.info("Unable to connect to shout server {}:{} - {}".format(options['ice_host'], options['ice_port'], ex))
exit()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -6,14 +6,14 @@ function getCookie(name) {
$(document).ready(function () { $(document).ready(function () {
var xsrf = getCookie("_xsrf"); var xsrf = getCookie("_xsrf");
$('#play_audio').click(function () { $('#play_audio').click(function () {
$.post("/a/play", { $.post("/a/play", JSON.stringify({
_xsrf: xsrf, _xsrf: xsrf,
audio_file: $('#audio_file').val() audio_file: $('#audio_file').val()
}); }), 'json');
}); });
$('#stop_audio').click(function () { $('#stop_audio').click(function () {
$.post("/a/stop", { $.post("/a/stop", {
_xsrf: xsrf _xsrf: xsrf
}); }, 'json');
}); });
}); });

View File

@@ -18,16 +18,12 @@ supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl] [supervisorctl]
serverurl=unix:///tmp/supervisor.sock serverurl=unix:///tmp/supervisor.sock
[program:icecast2]
user=icecast2
command=icecast2 -n -c /etc/icecast2/icecast.xml
stopsignal=6
stdout_events_enabled=true
stderr_events_enabled=true
autorestart=true
[program:server] [program:server]
command=server.py --ice_host=radio.deepsouthsounds.com --ice_port=8351 --ice_user=source --ice_password=RDzNlgqmj67vk --ice_mount=/dss command=server.py --ice_host=radio.deepsouthsounds.com \
--ice_port=8351 \
--ice_user=source \
--ice_password=RDzNlgqmj67vk \
--ice_mount=/dss
stopsignal=6 stopsignal=6
stdout_events_enabled=true stdout_events_enabled=true
stderr_events_enabled=true stderr_events_enabled=true