mirror of
https://github.com/fergalmoran/dss.radio.git
synced 2025-12-22 01:37:58 +00:00
Initial deploy
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -91,3 +91,5 @@ docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
config.ini
|
||||
32
Dockerfile
32
Dockerfile
@@ -1,24 +1,24 @@
|
||||
FROM ubuntu:wily
|
||||
MAINTAINER Fergal Moran "Ferg@lMoran.me"
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
FROM ubuntu:latest
|
||||
MAINTAINER Fergal Moran <Ferg@lMoran.me>
|
||||
|
||||
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"]
|
||||
@@ -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
|
||||
|
||||
@@ -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'])
|
||||
"""
|
||||
138
ice_relay.py
138
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]
|
||||
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()
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
71
server.py
71
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()
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user