Last active
January 2, 2018 19:04
-
-
Save daniel-j/2c8db53158f6000d6c7d to your computer and use it in GitHub Desktop.
Parasprite Radio files and utilities (for LiquidSoap and MPD)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
var net = require('net'); | |
var client = net.connect(1234, "localhost"); | |
client.pipe(process.stdout); | |
client.on('connect', function () { | |
console.log("Connected!"); | |
process.stdin.on('data', function (data) { | |
client.write(data.toString()+"\r\n"); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
var net = require('net'); | |
var client = net.connect(1234, "localhost"); | |
client.pipe(process.stdout); | |
client.on('connect', function () { | |
if (process.argv[2]) { | |
client.write("request.push /home/djazz/music/"+process.argv[2]+"\r\n"); | |
} | |
client.write("request.queue\r\n"); | |
client.write("quit\r\n"); | |
client.end(); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
while true | |
do | |
mpc -q idle playlist > /dev/null | |
filename=`mpc -q playlist -f %file% | head -n1` | |
echo "Queueing $filename" | |
node queue "$filename" | |
sleep 1 | |
mpc -q clear | |
done |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/liquidsoap | |
# Put the log file in some directory | |
set("log.file.path","./radio.log") | |
set("log.file", false) | |
# Print log messages to the console, | |
# can also be done by passing the -v option to liquidsoap. | |
set("log.stdout", true) | |
# Use the telnet server for requests | |
set("server.telnet", true) | |
set("server.telnet.bind_addr", "0.0.0.0") | |
set("server.telnet.port", 1234) | |
set("harbor.bind_addr", "0.0.0.0") | |
%include "credentials.liq" | |
%include "tunein.liq" | |
radio_name = "Parasprite Radio" | |
description = "Brony music 24/7!" | |
radio_url = "http://radio.djazz.se/" | |
radio_genre = "Pony" | |
icecast_host = "icecast.djazz.se" | |
icecast_port = 5000 | |
encoding = "UTF-8" | |
tunein_station_id = "s225092" | |
harbor_port = 7000 | |
meta = ref [] | |
# Mixes two streams, with faded transitions between the state when only the | |
# normal stream is available and when the special stream gets added on top of | |
# it. | |
# @category Source / Track Processing | |
# @param ~delay Delay before starting the special source. | |
# @param ~p Portion of amplitude of the normal source in the mix. | |
# @param ~normal The normal source, which could be called the carrier too. | |
# @param ~special The special source. | |
def smooth_add(~delay=0.5,~p=0.2,~normal,~special) | |
d = delay | |
fade.final = fade.final(duration=d*2.) | |
fade.initial = fade.initial(duration=d*2.) | |
q = 1. - p | |
c = amplify | |
fallback(track_sensitive=false, | |
[special,normal], | |
transitions=[ | |
fun(normal,special)-> | |
add(normalize=false, | |
[c(p,normal), | |
c(q,fade.final(type="sin",normal)), | |
sequence([blank(duration=d),c(q,special)])]), | |
fun(special,normal)-> | |
add(normalize=false, | |
[c(p,normal), | |
c(q,fade.initial(type="sin",normal))]) | |
]) | |
end | |
def scrobble(~user,~password,m) | |
audioscrobbler.nowplaying(user=user, password=password, m) | |
audioscrobbler.submit(user=user, password=password, m) | |
tunein.submit(partner_id=tunein_partner_id, partner_key=tunein_partner_key, station_id=tunein_station_id, m) | |
end | |
def update_nowplaying(m) | |
# is this needed? oh well.. | |
recode = string.recode(out_enc="UTF-8") | |
def f(x) = | |
(recode(fst(x)),recode(snd(x))) | |
end | |
meta := list.map(f,m) | |
data = json_of(compact=true, m) | |
# send metadata to script | |
system("node ../app/util/track-change.js "^quote(base64.encode(data))) | |
# update the info on server | |
ignore(http.post( | |
data = data, | |
headers = [("Content-Type", "application/json; charset=utf-8")], | |
timeout = 5.0, | |
"http://127.0.0.1:8000/liquidsoap-meta" | |
)) | |
end | |
# Return the json content | |
# of meta | |
def get_meta(~protocol,~data,~headers,uri) = | |
m = !meta | |
http_response( | |
protocol = protocol, | |
code = 200, | |
headers = [("Content-Type","application/json; charset=utf-8")], | |
data = json_of(compact=true, m) | |
) | |
end | |
# playlists | |
songs = mksafe(audio_to_stereo(playlist(reload_mode="watch", reload=600, prefix="/mnt/", "/home/djazz/.mpd/playlists/radio.m3u"))) | |
friendship = audio_to_stereo(playlist(reload_mode="watch", reload=600, "friendship.m3u")) | |
# queue: requests; announce: plays over music | |
queue = audio_to_stereo(request.queue(id="request")) | |
announce = audio_to_stereo(request.queue(id="announce")) | |
source = fallback([queue, songs]) | |
# scrobble music (last.fm & tunein) but not jingles etc.. | |
source = on_metadata(scrobble(user=lastfm_username, password=lastfm_password), source) | |
# TODO: replace with jingles | |
source = rotate(weights=[1,10], [friendship, source]) | |
# update now playing (and generate cover art..) | |
source = on_metadata(update_nowplaying, source) | |
# http endpoint to get metadata | |
harbor.http.register(port=harbor_port, method="GET", "/getmeta", get_meta) | |
# submit to Liquidsoap Flow | |
source = register_flow( | |
radio = radio_name, | |
description = description, | |
website = radio_url, | |
genre = radio_genre, | |
user = flow_username, | |
password = flow_password, | |
streams = [ | |
("mp3/320k", "http://radio.djazz.se/stream/"), | |
("aac/64k", "http://radio.djazz.se/stream/?mobile") | |
], | |
source | |
) | |
# play announcements over the music | |
source = smooth_add(delay=0.8, p=0.15, normal=source, special=announce) | |
# audio tweaking | |
#source = normalize(gain_max=3., gain_min=-3., source) | |
source = smart_crossfade(start_next=2., fade_in=2., fade_out=3., width=3., source) | |
output.icecast( | |
%mp3.cbr( | |
bitrate = 320, | |
stereo_mode = "joint_stereo", | |
internal_quality = 2, | |
id3v2 = true | |
), | |
host = icecast_host, | |
port = icecast_port, | |
mount = "radio", | |
password = icecast_password, | |
name = radio_name, | |
description = description, | |
url = radio_url, | |
genre = radio_genre, | |
encoding = encoding, | |
source | |
) | |
output.icecast( | |
%fdkaac(bitrate=64), | |
host = icecast_host, | |
port = icecast_port, | |
mount = "radio_mobile", | |
password = icecast_password, | |
name = radio_name, | |
description = description, | |
url = radio_url, | |
genre = radio_genre, | |
encoding = encoding, | |
source | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
'use strict'; | |
var mpd = require('mpd'); | |
var fs = require('fs'); | |
var cmd = mpd.cmd; | |
var client = mpd.connect({ | |
port: 6600, | |
host: '127.0.0.1' | |
}); | |
function getPlaylist() { | |
console.log("Reading playlist..."); | |
client.sendCommand(cmd("listplaylistinfo", ['radio']), function(err, msg) { | |
if (err) { | |
console.error(err); | |
return; | |
} | |
var playlist = msg.split("\nfile: "); | |
for (var i = 0; i < playlist.length; i++) { | |
if (i > 0) { | |
playlist[i] = "file: "+playlist[i]; | |
} | |
playlist[i] = playlist[i].split("\n"); | |
if (i === playlist.length-1) { | |
playlist[i].pop(); | |
} | |
var o = {}; | |
for (var j = 0; j < playlist[i].length; j++) { | |
var line = playlist[i][j]; | |
var pos = line.indexOf(": "); | |
if (pos !== -1) { | |
var key = line.substr(0, pos).toLowerCase(); | |
var value = line.substr(pos+2); | |
o[key] = value; | |
} | |
} | |
o['time'] = +o['time']; | |
delete o['file']; | |
delete o['last-modified']; | |
delete o['date']; | |
playlist[i] = o; | |
} | |
fs.writeFile(__dirname+'/playlist.json', JSON.stringify(playlist), function (err) { | |
if (err) { | |
console.error("Unable to write file. "+err); | |
return; | |
} | |
console.log("Playlist updated!"); | |
}); | |
}); | |
} | |
client.on('ready', function() { | |
console.log("ready"); | |
getPlaylist(); | |
}); | |
client.on('system', function(name) { | |
console.log("update", name); | |
if (name === "stored_playlist" || name === "database") { | |
getPlaylist(); | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
'use strict'; | |
var fs = require('fs') | |
var path = require('path') | |
var mm = require('musicmetadata') | |
var lwip = require('lwip') | |
function typeToMime(type) { | |
switch (type) { | |
case 'jpg': type = 'image/jpeg'; break | |
case 'jpeg': type = 'image/jpeg'; break | |
case 'png': type = 'image/png'; break | |
default: type = null; break | |
} | |
return type | |
} | |
function imageFromFile(filename, cb) { | |
var stream = fs.createReadStream(filename) | |
var gotimg = false | |
//allowed = ['.mp3', '.ogg', '.flac', '.wma'] | |
//if allowed.indexOf(path.extname(filename).toLowerCase()) == -1 | |
// cb 'non-allowed file type' | |
// return | |
var parser = mm(stream, {}, function (err, meta) { | |
var pictures = meta.picture | |
if (pictures && pictures[0]) { | |
var type = typeToMime(pictures[0].format) | |
if (type !== null) { | |
cb(null, type, meta.picture[0].data) | |
gotimg = true | |
} | |
} | |
if (!gotimg) { | |
var dir = path.dirname(filename) | |
fs.readdir(dir, function (err, result) { | |
if (err) { | |
cb(err) | |
return | |
} | |
var valid = ['.png', '.jpg', '.jpeg'] | |
var commonFiles = ['cover', 'folder', 'album', 'artist', 'art'] | |
result = result.filter(function (f) { | |
var ext = path.extname(f).toLowerCase() | |
return valid.indexOf(ext) !== -1 | |
}) | |
var img = null | |
for (var i = 0; i < result.length; i++) { | |
var file = result[i] | |
if (img !== null) break | |
var f = file.toLowerCase() | |
if (commonFiles.indexOf(path.basename(f, path.extname(f))) !== -1) { | |
img = file | |
} | |
else { | |
for (var j = 0; j < commonFiles.length; j++) { | |
var common = commonFiles[j] | |
if (f.indexOf(common) !== -1) { | |
img = file | |
break | |
} | |
} | |
} | |
} | |
if (img == null) { | |
cb('no image found\n'+JSON.stringify(meta)) | |
} | |
else { | |
fs.readFile(path.join(dir, img), function (err, data) { | |
if (err) { | |
cb(err) | |
} | |
else { | |
cb(null, typeToMime(path.extname(img).substr(1)), data) | |
} | |
}) | |
} | |
}) | |
//res.sendFile path.join(filename+'/../cover.jpg'), | |
// root: config.media_dir | |
} | |
}) | |
parser.on('done', function (err) { | |
stream.destroy() | |
if (err && !gotimg) | |
cb(err) | |
}) | |
parser.on('error', function (err) { | |
stream.destroy() | |
if (err && !gotimg) | |
cb(err) | |
}) | |
stream.on('error', function (err) { | |
if (!gotimg) | |
cb(err) | |
}) | |
} | |
var liq = JSON.parse(new Buffer(process.argv[2], "base64").toString('utf8')) | |
console.log(liq); | |
var json = { | |
title: liq.title || path.basename(liq.filename, path.extname(liq.filename)), | |
artist: liq.artist || null, | |
album: liq.album || null, | |
albumartist: liq.albumartist || null, | |
url: liq.url || null, | |
year: +liq.year || null | |
} | |
fs.writeFile(__dirname+'/now/json', JSON.stringify(json), 'utf8') | |
console.log("Generating art...") | |
imageFromFile(liq.filename, function (err, type, data) { | |
if (err) { | |
console.log("Failed to generate art! "+err) | |
fs.unlink(__dirname+'/now/image-full', function () {}) | |
fs.unlink(__dirname+'/now/image-small.png', function () {}) | |
fs.unlink(__dirname+'/now/image-tiny.png', function () {}) | |
fs.unlink(__dirname+'/now/type.txt', function () {}) | |
} | |
else { | |
fs.writeFile(__dirname+'/now/image-full', data, function (err) { | |
if (err) throw err | |
console.log("Saved full art") | |
}) | |
fs.writeFile(__dirname+'/now/type.txt', type) | |
var t = type.split('/')[1] === 'png' ? 'png':'jpg' | |
lwip.open(data, t, function (err, image) { | |
if (err) console.log(err) | |
else { | |
image.batch() | |
.cover(80, 80) | |
.writeFile(__dirname+'/now/image-tiny.png', function (err) { | |
if (err) throw err | |
console.log("Saved tiny art") | |
}) | |
} | |
}) | |
lwip.open(data, t, function (err, image) { | |
if (err) console.log(err) | |
else { | |
image.batch() | |
.cover(350, 350) | |
.writeFile(__dirname+'/now/image-small.png', function (err) { | |
if (err) throw err | |
console.log("Saved small art") | |
}) | |
} | |
}) | |
} | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment