Skip to content

Instantly share code, notes, and snippets.

@hecanjog
Last active April 27, 2020 04:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hecanjog/bde84e4a147b6449ab3e024c26d6537a to your computer and use it in GitHub Desktop.
Save hecanjog/bde84e4a147b6449ab3e024c26d6537a to your computer and use it in GitHub Desktop.
phonography.radio.af setup
<!doctype html>
<html>
<head>
<title>Phonography on Aleatoric Forest</title>
<link rel="stylesheet" href="/style.css?v=2019-06-15" />
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, user-scalable=no" />
</head>
<body>
<header>
<h1><em>Phonography</em> on Aleatoric Forest</h1>
</header>
<img id="phonography-cover" />
<section class="player">
<button id="phonography-toggle">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 100 125"
enable-background="new 0 0 100 100"
xml:space="preserve"
>
<polygon fill="#ffffff" points="4.948,21.713 53.945,50.002 4.948,78.286 "/>
<rect x="53.945" y="21.713" fill="#ffffff" width="15.478" height="56.573"/>
<rect x="80.77" y="21.713" fill="#ffffff" width="15.48" height="56.573"/>
</svg>
<span id="button-test">Play</span>
</button>
<audio id="phonography-player"></audio>
</section>
<section class="info">
<p>Now Playing: <br/><span id="phonography-info"></span></p>
</section>
<footer>
<cite><a href="https://thenounproject.com/term/play-pause/86425/"><i>play pause</i> by Sylvain A.</a> from the Noun Project</cite>
</footer>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', e => {
let button = document.getElementById('phonography-toggle');
let buttontext = document.getElementById('button-test');
let player = document.getElementById('phonography-player');
let meta = document.getElementById('phonography-info');
let cover = document.getElementById('phonography-cover');
let metalistener = () => {
es = new EventSource('/info/metadata-stream');
es.addEventListener('meta', e => {
let d = JSON.parse(e.data);
meta.innerText = d.title;
cover.src = `/covers/${ d.catalognumber }.jpg`;
}, false);
es.onerror = () => {
if(es.readyState === 2) {
es.close();
es = null;
setTimeout(() => {
metalistener();
}, 1000);
}
};
};
metalistener();
function seed_metadata() {
let d = JSON.parse(this.responseText);
meta.innerText = d.title;
cover.src = `/covers/${ d.catalognumber }.jpg`;
}
let request = new XMLHttpRequest();
request.addEventListener('load', seed_metadata);
request.open('GET', '/info/metadata-seed');
request.send();
player.pause();
button.onclick = (e) => {
e.preventDefault();
if(player.paused) {
player.src = 'http://phonography.radio.af/stream/';
player.play();
button.classList.add('playing');
buttontext.innerText = 'Pause';
} else {
player.src = 'http://phonography.radio.af/stream/';
player.pause();
button.classList.remove('playing');
buttontext.innerText = 'Play';
}
};
});
</script>
</body>
</html>
[Unit]
Description=phonography info gunicorn service
After=network.target
[Service]
User=deploy
Group=www-data
Restart=on-failure
WorkingDirectory=/srv/radio/phonography
Environment="PATH=/srv/radio/phonography/venv/bin"
ExecStart=/srv/radio/phonography/venv/bin/gunicorn --workers 2 --worker-class gevent --bind 127.0.0.1:3002 --reload phonography_info:app
[Install]
WantedBy=multi-user.target
[Unit]
Description=phonography liquidsoap streamer
After=network.target
[Service]
User=deploy
Group=www-data
Restart=on-failure
WorkingDirectory=/srv/radio/phonography
ExecStart=/home/deploy/.opam/system/bin/liquidsoap /srv/radio/phonography/phonography.liq
[Install]
WantedBy=multi-user.target
#!/usr/bin/liquidsoap
set('log.file.path', '/srv/radio/phonography/phonography.log')
set('log.stdout', false)
set('harbor.bind_addr', '127.0.0.1')
url = 'http://your-ip-addess'
streamuser = 'source'
streampass = 'hackme'
phonography = nrj(playlist('/srv/radio/phonography/phonography.m3u', mode='randomize', reload=1, reload_mode='rounds'))
radio = mksafe(phonography)
def post_metadata(m) =
system("./venv/bin/python phonography_info.py "^quote(m['title'])^" "^quote(m['catalognumber']))
end
radio = amplify(1.0, override="replay_gain", radio)
radio = on_track(post_metadata(), radio)
radio = smart_crossfade(fade_out=10.0, fade_in=10.0, radio)
output.icecast(%mp3.vbr(quality=0, samplerate=44100, internal_quality=0, id3v2=true), radio,
host='localhost', port=8000, user=streamuser, password=streampass,
description='Phonography on Aleatoric Forest', url=url, genre='Field Recording',
mount='phonography', name='Phonography on Aleatoric Forest')
<VirtualHost *:80>
ServerName phonography.radio.af
ServerAdmin contact@luvsound.org
DocumentRoot /srv/www/phonography.radio.af
ErrorLog ${APACHE_LOG_DIR}/phonography.radio.af-error.lg
CustomLog ${APACHE_LOG_DIR}/phonography.radio.af-access.log combined
XBitHack On
ProxyPreserveHost On
ProxyPass /stream/ http://localhost:8000/phonography
ProxyPassReverse /stream/ http://localhost:8000/phonography
ProxyPass /info http://localhost:3002/
ProxyPassReverse /info http://localhost:3002/
<Directory /srv/www/phonography.radio.af>
Require all granted
ExpiresActive On
ExpiresDefault "access plus 120 seconds"
Options +Includes
</Directory>
<FilesMatch "\.(cgi|wsgi)$">
SSLOptions +StdEnvVars
</FilesMatch>
</VirtualHost>
from flask import Flask, Response
from redis import StrictRedis
import tweepy
import logging
from logging.handlers import SysLogHandler
import sys
import json
app = Flask(__name__)
r = StrictRedis(host='127.0.0.1', port=6379)
logger = logging.getLogger('phonography-info')
logger.setLevel(logging.DEBUG)
logger.addHandler(SysLogHandler(address='/dev/log'))
def info_stream():
ps = r.pubsub()
ps.subscribe('meta')
for msg in ps.listen():
logger.info('info_stream msg %s' % msg)
if msg['type'] == 'message':
yield 'event: meta\ndata: %s\n\n' % msg['data'].decode('utf-8')
@app.route('/metadata-seed')
def info_seed():
return json.dumps({
'title': r.get('phonography_current_title').decode('utf-8'),
'catalognumber': r.get('phonography_current_catalognumber').decode('utf-8'),
})
@app.route('/metadata-stream')
def stream():
resp = Response(info_stream(), mimetype="text/event-stream")
resp.headers['Connection'] = 'keep-alive'
resp.headers['Cache-Control'] = 'no-store'
return resp
if __name__ == '__main__':
try:
title = sys.argv[1]
catalognumber = sys.argv[2]
msg = json.dumps(dict(title=title, catalognumber=catalognumber))
r.publish(channel='meta', message=msg)
r.set('phonography_current_title', title)
r.set('phonography_current_catalognumber', catalognumber)
except IndexError:
pass
Click==7.0
Flask==1.0.2
gunicorn==19.9.0
itsdangerous==1.1.0
Jinja2==2.10
MarkupSafe==1.1.0
redis==3.0.1
six==1.11.0
Werkzeug==0.14.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment