From 9092c04766c36fdfd6c8bbe7e87d0280921373b1 Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Mon, 5 Oct 2015 20:14:33 +0100 Subject: [PATCH] Initial deploy --- .gitignore | 2 + Dockerfile | 32 +++++----- default/icecast2 | 19 ------ dss.radio.conf | 4 ++ ice_client.py | 36 ----------- ice_relay.py | 146 +++++++++++++++++++++++++++++++++++-------- icecast2/icecast.xml | 51 --------------- requirements.txt | 4 +- server.py | 71 +++++++++------------ static/js/app.js | 6 +- supervisord.conf | 14 ++--- 11 files changed, 181 insertions(+), 204 deletions(-) delete mode 100644 default/icecast2 create mode 100644 dss.radio.conf delete mode 100755 ice_client.py delete mode 100755 icecast2/icecast.xml diff --git a/.gitignore b/.gitignore index 5245ce0..a1a6b75 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,5 @@ docs/_build/ # PyBuilder target/ + +config.ini \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0011b57..a60c059 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,24 @@ -FROM ubuntu:wily -MAINTAINER Fergal Moran "Ferg@lMoran.me" -ENV DEBIAN_FRONTEND noninteractive +FROM ubuntu:latest +MAINTAINER Fergal Moran -RUN mkdir /code +RUN apt-get update -y +RUN apt-get upgrade -y -RUN apt-get -qq -y update && \ - 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 apt-get install -y python-setuptools python-pip git pkg-config libshout3 libshout3-dev python-dev +RUN mkdir /code/ WORKDIR /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/ -ADD default/icecast2 /etc/default/ -ADD supervisord.conf /etc/supervisord.conf - +# Install tornado 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"] \ No newline at end of file diff --git a/default/icecast2 b/default/icecast2 deleted file mode 100644 index 3a2521f..0000000 --- a/default/icecast2 +++ /dev/null @@ -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 - diff --git a/dss.radio.conf b/dss.radio.conf new file mode 100644 index 0000000..2823881 --- /dev/null +++ b/dss.radio.conf @@ -0,0 +1,4 @@ +ice_password="RDzNlgqmj67vk" +ice_host="radio.deepsouthsounds.com" +ice_mount="/dss" +api_host="api.deepsouthsounds.com" \ No newline at end of file diff --git a/ice_client.py b/ice_client.py deleted file mode 100755 index 791d9ea..0000000 --- a/ice_client.py +++ /dev/null @@ -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']) -""" diff --git a/ice_relay.py b/ice_relay.py index e121274..30ec42e 100644 --- a/ice_relay.py +++ b/ice_relay.py @@ -1,61 +1,155 @@ +import logging from threading import Thread import time -from deefuzzer import Player +import urllib +import shout +import urllib2 +import requests BUF_LEN = 4096 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__() self.title = title - self.s = client self._ended = True - self.player = Player("icecast") self.isOpen = True self.audio_queue = [] self.audio_index = 0 - self.default_queue = [ - 'https://dsscdn.blob.core.windows.net/mixes/7568d3a4-9a9f-4f0f-a900-f84231c26c47.mp3' - ] + self.channelIsOpen = False - 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 + 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): self.audio_queue = queue + self._ended = True def get_next_play_item(self): - print "Finding next item" - self._ended = False + try: + logging.debug("Finding next item") - # get random item from DSS api - if len(self.audio_queue) > self.audio_index: - item = self.audio_queue[self.audio_index] - else: - item = self.default_queue[0] + # get random item from DSS api + if len(self.audio_queue) > self.audio_index: + item = self.audio_queue[self.audio_index] + else: + item = self.default_queue()[0] - self.player.set_media(item) - print "Playing: {}".format(item) - return self.player.file_read_remote() + logging.debug("Playing: {}".format(item)) + self.stream = self.file_read_remote(item) - def close_channel(self): - self.isOpen = False + self._ended = False + return True + except Exception as ex: + logging.error('Error getting next play item: {}'.format(ex)) + + return False def run(self): + self.ping_server() while True: now_playing = self.get_next_play_item() if now_playing is not None: - for chunk in now_playing: + for self.chunk in self.stream: try: - self.s.s.send(chunk) - self.s.s.sync() + self.channel.send(self.chunk) + self.channel.sync() except Exception as ex: - print ("Error sending chunk: {0}".format(ex)) - self.close_channel() + logging.error("Error sending chunk: {0}".format(ex)) + self.channel_close() if self._ended: break else: - print("No audio, waiting") + logging.debug("No audio, waiting") 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() diff --git a/icecast2/icecast.xml b/icecast2/icecast.xml deleted file mode 100755 index b13de92..0000000 --- a/icecast2/icecast.xml +++ /dev/null @@ -1,51 +0,0 @@ - - Cork Like - icemaster@deepsouthsounds.com - - - 100 - 2 - 5 - 524288 - 30 - 15 - 10 - 1 - 65535 - - - - RDzNlgqmj67vk - 9PmUbI1mLne9o - - admin - CrVuP5evoJZ0. - - - radio.deepsouthsounds.com - - - 8351 - /dss - - 1 - - - /usr/share/icecast2 - /var/log/icecast2 - /usr/share/icecast2/web - /usr/share/icecast2/admin - - - - - access.log - error.log - 3 - 10000 - - - - 0 - - diff --git a/requirements.txt b/requirements.txt index 9468055..40ee6c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ +twisted cython requests 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 \ No newline at end of file diff --git a/server.py b/server.py index 4f6a9d3..145172d 100755 --- a/server.py +++ b/server.py @@ -1,28 +1,16 @@ -#!/usr/bin/env python import logging - -from shout import ShoutException +import os +import signal import time import tornado import tornado.ioloop import tornado.web import tornado.escape from tornado.options import define, options, parse_command_line -import os -import signal -from ice_client import IceClient + from ice_relay import IceRelay -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=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)") +is_closing = False class MainHandler(tornado.web.RequestHandler): @@ -37,7 +25,6 @@ class PlayAudioHandler(tornado.web.RequestHandler): in_file = data.get('audio_file') if in_file is not None: relay.set_audio_queue([in_file]) - relay.stop() time.sleep(10) except Exception, ex: raise tornado.web.HTTPError(500, ex.message) @@ -46,12 +33,10 @@ class PlayAudioHandler(tornado.web.RequestHandler): class StopAudioHandler(tornado.web.RequestHandler): def post(self, *args, **kwargs): try: - relay.stop() + relay.channel_close() except Exception, ex: raise tornado.web.HTTPError(500, ex.message) -is_closing = False - def signal_handler(signum, frame): global is_closing @@ -63,14 +48,35 @@ def try_exit(): global is_closing if is_closing: # clean up here - streamer.stop() - relay.stop() + relay.channel_close() tornado.ioloop.IOLoop.instance().stop() 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(): + if relay.channel_open(): + relay.start() + else: + logging.error("IceCast relay failed to start") + exit() + app = tornado.web.Application( [ (r"/", MainHandler), @@ -81,30 +87,13 @@ def main(): template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), # xsrf_cookies=True, - debug=options.debug + debug=options['debug'] ) signal.signal(signal.SIGINT, signal_handler) - app.listen(options.port) + app.listen(options['port']) tornado.ioloop.PeriodicCallback(try_exit, 100).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__': main() diff --git a/static/js/app.js b/static/js/app.js index c4dfbe5..c088dc5 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -6,14 +6,14 @@ function getCookie(name) { $(document).ready(function () { var xsrf = getCookie("_xsrf"); $('#play_audio').click(function () { - $.post("/a/play", { + $.post("/a/play", JSON.stringify({ _xsrf: xsrf, audio_file: $('#audio_file').val() - }); + }), 'json'); }); $('#stop_audio').click(function () { $.post("/a/stop", { _xsrf: xsrf - }); + }, 'json'); }); }); \ No newline at end of file diff --git a/supervisord.conf b/supervisord.conf index d56b041..aaf294c 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -18,16 +18,12 @@ supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] 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] -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 stdout_events_enabled=true stderr_events_enabled=true