diff --git a/client/djrandom_client/client.py b/client/djrandom_client/client.py index d99fe036a8afb4786a2962f4edd0bc42b5e297bd..0baee71c3ee486df81479e705b0cdbf2eb6a137c 100644 --- a/client/djrandom_client/client.py +++ b/client/djrandom_client/client.py @@ -124,6 +124,10 @@ def main(): if args: parser.error('Too many arguments') + # Perform a version check. + if utils.check_version(): + print >>sys.stderr, 'A new release is available! Please update.' + # Reading from the configuration file will set this variable to # a string, convert it back into boolean. do_realtime = not opts.no_realtime_watch diff --git a/client/djrandom_client/utils.py b/client/djrandom_client/utils.py index 5efa285596b3b57dc273e73799eb71e1f20631f0..e9fd9d046aa607c899a8afded1be57c6e5d14c81 100644 --- a/client/djrandom_client/utils.py +++ b/client/djrandom_client/utils.py @@ -1,7 +1,10 @@ import hashlib import os +import urllib2 +from djrandom_client import version NESTING = 2 +VERSION_PY_URL = 'https://git.autistici.org/djrandom/plain/client/djrandom_client/version.py' def generate_path(base_dir, sha1): @@ -48,3 +51,31 @@ def read_config_defaults(parser, path): path, 1 + linenum)) var, value = map(lambda x: x.strip(), line.split('=', 1)) parser.set_default(var, _unquote(value)) + + +def _split_version_string(s): + def _toint(x): + try: + return int(x) + except: + return x + return tuple(map(_toint, s.split('.'))) + + +def check_version(): + """Returns True if we need to upgrade.""" + try: + last_version_str = urllib2.urlopen(VERSION_PY_URL).read() + except: + return False + + match = re.match(r'VERSION\s*=\s*[\'"]([0-9a-z.]+)[\'"]', + last_version_str.strip()) + if not match: + return False + last_version_t = _split_version_string(match.group(1)) + cur_version_t = _split_version_string(version.VERSION) + + return cur_version_t <= last_version_t + + diff --git a/client/djrandom_client/version.py b/client/djrandom_client/version.py new file mode 100644 index 0000000000000000000000000000000000000000..68c0733348c65f5f45bd043f1e94bc7b1bd30d48 --- /dev/null +++ b/client/djrandom_client/version.py @@ -0,0 +1 @@ +VERSION = '0.2' diff --git a/client/setup.py b/client/setup.py index aad077744a396a69b9386489b50c30ad581863cb..60cd80bfc16b4648b368844c62d207606ed8baee 100644 --- a/client/setup.py +++ b/client/setup.py @@ -1,5 +1,10 @@ #!/usr/bin/python +# Read version from version.py. +import sys, os +sys.path.insert(0, os.path.dirname(__file__)) +from djrandom_client import version + from setuptools import setup, find_packages import platform if platform.system() == 'Darwin': @@ -9,7 +14,7 @@ else: setup( name="djrandom-client", - version="0.1", + version=version.VERSION, description="DJ:Random client", author="ale", author_email="ale@incal.net", diff --git a/server/djrandom/fingerprint/__init__.py b/server/djrandom/fingerprint/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/server/djrandom/fingerprint/dedup.py b/server/djrandom/fingerprint/dedup.py new file mode 100644 index 0000000000000000000000000000000000000000..e4e7077c4249952d876c26b5b3aedd8e66673ff9 --- /dev/null +++ b/server/djrandom/fingerprint/dedup.py @@ -0,0 +1,87 @@ +import fp +import os +import optparse +import logging +import json +from djrandom import daemonize +from djrandom import utils +from djrandom.model.mp3 import MP3 +from djrandom.database import Session, init_db + + +# Taken from 'fastingest.py', with minor changes. +def generate_code_json(jdata, track_id): + c = json.loads(jdata) + if "code" not in c: + return {} + + code = c["code"] + m = c["metadata"] + length = m["duration"] + version = m["version"] + artist = m.get("artist", None) + title = m.get("title", None) + release = m.get("release", None) + decoded = fp.decode_code_string(code) + + data = {"track_id": track_id, + "fp": decoded, + "length": length, + "codever": "%.2f" % version + } + if artist: data["artist"] = artist + if release: data["release"] = release + if title: data["track"] = title + return data + + +def dedupe_db(): + + codes = {} + + # Load all known fingerprints into the db. + mp3s = MP3.query.filter( + (MP3.ready == True) & (MP3.error == False) + & (MP3.echoprint_fp != None)) + for mp3 in mp3s: + code = generate_code_json(mp3.echoprint_fp, mp3.sha1) + if not code: + continue + codes[mp3.sha1] = code['fp'] + fp.ingest([code], do_commit=False, local=True) + + # Now dedupe by going through all our codes over again. + for sha1, code in codes.iteritems(): + results = fp.query_fp(code, local=True).results + if len(results) < 2: + continue + print 'SHA1: %s' % sha1 + for entry in results: + if entry['track_id'] == sha1: + continue + print ' --> %s (%s)' % (entry['track_id'], entry['score']) + + +def run_deduper(db_url): + init_db(db_url) + dedupe_db() + + +def main(): + parser = optparse.OptionParser() + parser.add_option('--db_url') + daemonize.add_standard_options(parser) + utils.read_config_defaults( + parser, os.getenv('DJRANDOM_CONF', '/etc/djrandom.conf')) + opts, args = parser.parse_args() + if not opts.db_url: + parser.error('Must provide --db_url') + if args: + parser.error('Too many arguments') + + daemonize.daemonize(opts, run_deduper, + (opts.db_url,)) + + +if __name__ == '__main__': + main() diff --git a/server/djrandom/fingerprint/fingerprint.py b/server/djrandom/fingerprint/fingerprint.py new file mode 100644 index 0000000000000000000000000000000000000000..14e6c44583954164e069b5e015416e74a23190ea --- /dev/null +++ b/server/djrandom/fingerprint/fingerprint.py @@ -0,0 +1,81 @@ +import os +import optparse +import logging +import subprocess +import time +import traceback +from djrandom import daemonize +from djrandom import utils +from djrandom.model.mp3 import MP3 +from djrandom.database import Session, init_db + +log = logging.getLogger(__name__) + + +class Fingerprinter(object): + + def __init__(self, codegen_path): + self.codegen_path = codegen_path + + def process(self, mp3): + pipe = subprocess.Popen( + [self.codegen_path, mp3.path, '10', '30'], + close_fds=False, + stdout=subprocess.PIPE) + fp_json = pipe.communicate()[0] + if fp_json: + # Remove the square brackets that make fp_json an array. + # (Ugly Hack!) + mp3.echoprint_fp = fp_json[2:-2] + + def compute_fingerprints(self, run_once): + """Compute fingerprints of new files.""" + while True: + mp3 = MP3.query.filter(MP3.echoprint_fp == None + ).limit(1).first() + if not mp3: + if run_once: + break + Session.remove() + self.idx.commit() + time.sleep(60) + continue + log.info('fingerprinting %s' % mp3.sha1) + try: + self.process(mp3) + except Exception, e: + log.error(traceback.format_exc()) + Session.add(mp3) + Session.commit() + + +def run_fingerprinter(db_url, codegen_path, run_once): + init_db(db_url) + scanner = Fingerprinter(codegen_path) + scanner.compute_fingerprints(run_once) + + +def main(): + parser = optparse.OptionParser() + parser.add_option('--once', action='store_true') + parser.add_option('--codegen_path', + default='/usr/local/bin/echoprint-codegen') + parser.add_option('--db_url') + daemonize.add_standard_options(parser) + utils.read_config_defaults( + parser, os.getenv('DJRANDOM_CONF', '/etc/djrandom.conf')) + opts, args = parser.parse_args() + if not opts.db_url: + parser.error('Must provide --db_url') + if args: + parser.error('Too many arguments') + + if opts.once: + opts.foreground = True + + daemonize.daemonize(opts, run_fingerprinter, + (opts.db_url, opts.codegen_path, opts.once)) + + +if __name__ == '__main__': + main() diff --git a/server/djrandom/frontend/api_views.py b/server/djrandom/frontend/api_views.py index 96f421a4f1f9d57e680f587e26fcf9178d125861..e4cf6aec432a7cfe9466bd68738c235d354ef530 100644 --- a/server/djrandom/frontend/api_views.py +++ b/server/djrandom/frontend/api_views.py @@ -151,7 +151,33 @@ def more_like_these_json(): @app.route('/json/most_played', methods=['GET']) @require_auth def most_played_json(): + n = int(request.args.get('n', 20)) most_played = [{'sha1': sha1, 'count': count} - for sha1, count in PlayLog.most_played(20)] + for sha1, count in PlayLog.most_played(n)] return jsonify(results=most_played) + +@app.route('/json/last_uploaded', methods=['GET']) +@require_auth +def last_uploaded_json(): + n = int(request.args.get('n', 20)) + last_uploaded = [x.sha1 for x in MP3.last_uploaded(n)] + return jsonify(results=last_uploaded) + + +@app.route('/json/markov', methods=['POST']) +@require_auth +def markov_json(): + n = int(request.form.get('n', 10)) + hashes = request.form.get('h', '').split(',') + last_song = hashes[-1] + sequence = svcs['markov'].generate_sequence(last_song, 2, n) + return jsonify(results=sequence) + + +@app.route('/json/random', methods=['GET']) +@require_auth +def random_json(): + n = int(request.args.get('n', 10)) + random_songs = [x.sha1 for x in MP3.get_random_songs(n)] + return jsonify(results=random_songs) diff --git a/server/djrandom/frontend/frontend.py b/server/djrandom/frontend/frontend.py index 925c11e1e3bf04c0a65340dc8d8d99e8c566a924..c0ed117cb9330a5c696a9af812b41df488fc06c4 100644 --- a/server/djrandom/frontend/frontend.py +++ b/server/djrandom/frontend/frontend.py @@ -11,6 +11,7 @@ from djrandom.database import init_db from djrandom.frontend import app, svcs from djrandom.frontend.mailer import Mailer from djrandom.frontend.search import Searcher +from djrandom.model.markov import MarkovModel from djrandom.model.external import AlbumImageRetriever from gevent.wsgi import WSGIServer @@ -18,12 +19,18 @@ log = logging.getLogger(__name__) def run_frontend(port, solr_url, db_url, lastfm_api_key, album_art_dir, - email_sender): + email_sender, markov_data_file): init_db(db_url) svcs['searcher'] = Searcher(solr_url) svcs['album_images'] = AlbumImageRetriever(lastfm_api_key, album_art_dir) svcs['mailer'] = Mailer(email_sender) + svcs['markov'] = MarkovModel() + try: + svcs['markov'].load(markov_data_file) + except IOError, e: + log.error('Could not read Markov data from %s: %s' % ( + markov_data_file, str(e))) http_server = WSGIServer(('0.0.0.0', port), app) http_server.serve_forever() @@ -37,6 +44,8 @@ def main(): parser.add_option('--lastfm_api_key') parser.add_option('--email_sender', default='djrandom@localhost') parser.add_option('--album_art_dir', default='/var/tmp/album-image-cache') + parser.add_option('--markov_data', + default='/var/lib/djrandom/djrandom-markov.dat') daemonize.add_standard_options(parser) utils.read_config_defaults( parser, os.getenv('DJRANDOM_CONF', '/etc/djrandom.conf')) @@ -49,7 +58,7 @@ def main(): daemonize.daemonize(opts, run_frontend, (opts.port, opts.solr_url, opts.db_url, opts.lastfm_api_key, opts.album_art_dir, - opts.email_sender), + opts.email_sender, opts.markov_data), support_gevent=True) diff --git a/server/djrandom/frontend/static/css/style.css b/server/djrandom/frontend/static/css/style.css index 2968a986c11399f3e565e1ae9f4ef36d0ae9d967..c052cd69c7c8df9a5f9e79b5d63bd43ec0f0ee5d 100644 --- a/server/djrandom/frontend/static/css/style.css +++ b/server/djrandom/frontend/static/css/style.css @@ -28,10 +28,19 @@ body { font-size: 0.9em; } -input { +#queryField { + width:75%; font-size: 1.1em; + border:1px solid red; } +a#playlistLast25,a#playlistRandom,a#playlistMost { + font-size:0.8em; +} + +a#playlistExtendBtn,a#playlistClearBtn,a#playlistStreamBtn { + font-size:0.8em; +} h1 { font-size: 66px; letter-spacing: -4px; diff --git a/server/djrandom/frontend/static/js/djr.min.js b/server/djrandom/frontend/static/js/djr.min.js index a4a83704aeffc42978668c1f898eb0cfffae92f1..81fe21552500ec84ba759e3e1175f4d940dbe9cf 100644 --- a/server/djrandom/frontend/static/js/djr.min.js +++ b/server/djrandom/frontend/static/js/djr.min.js @@ -1,6 +1,7 @@ djr={};var CHARS="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");djr.generateRandomId=function(){var a=[],b=CHARS,c,d=b.length;for(c=0;c<40;c++)a[c]=b[0|Math.random()*d];return a.join("")};djr.Backend=function(){};djr.Backend.prototype.search=function(a,b,c){$.ajax({url:"/json/search",data:{q:a},dataType:"json",type:"GET",context:c||this,success:function(a){b(a.results)}})};djr.Backend.prototype.moreLikeThese=function(a,b,c){$.ajax({url:"/json/morelikethese",data:{h:a.join(",")},dataType:"json",type:"POST",context:c||this,success:function(a){b(a.results)}})}; djr.Backend.prototype.getPlaylist=function(a,b,c){$.ajax({url:"/json/playlist/get/"+a,dataType:"json",type:"GET",context:c||this,success:function(c){b(new djr.PlaylistChunk(c.songs,a))}})};djr.Backend.prototype.savePlaylist=function(a,b){$.ajax({url:"/json/playlist/save",data:{uuid:a,h:b.join(",")},type:"POST"})};djr.Backend.prototype.streamPlaylist=function(a,b,c,d){$.ajax({url:"/json/playlist/stream",data:{uuid:a,stream:b?"y":"n"},dataType:"json",type:"POST",context:d||this,success:function(a){c(a)}})}; -djr.Backend.prototype.getHtmlForSongs=function(a,b,c){$.ajax({url:"/fragment/songs",data:{h:a.join(",")},dataType:"html",type:"POST",context:c||this,success:b})};djr.Backend.prototype.nowPlaying=function(a,b){$.ajax({url:"/json/playing",data:{cur:a,prev:b.join(",")},type:"POST"})};djr.PlaylistChunk=function(a,b){this.songs=a||[];this.title=b};djr.PlaylistChunk.prototype.hasSong=function(a){return this.songs.indexOf(a)>=0};djr.PlaylistChunk.prototype.removeSong=function(a){this.songs=$.grep(this.songs,function(b){return b!=a})};djr.controlButtons=function(a){return'<div class="ctlbox" style="display:none"><a id="'+a+'_remove" class="ctl_btn ctl_remove"> </a></div>'}; +djr.Backend.prototype.getHtmlForSongs=function(a,b,c){$.ajax({url:"/fragment/songs",data:{h:a.join(",")},dataType:"html",type:"POST",context:c||this,success:b})};djr.Backend.prototype.nowPlaying=function(a,b){$.ajax({url:"/json/playing",data:{cur:a,prev:b.join(",")},type:"POST"})};djr.Backend.prototype.mostPlayedPlaylist=function(a,b,c){$.ajax({url:"/json/most_played",data:{n:a},dataType:"json",type:"GET",context:c||this,success:function(a){b(a.results)}})}; +djr.Backend.prototype.lastPlaylist=function(a,b,c){$.ajax({url:"/json/last_uploaded",data:{n:a},dataType:"json",type:"GET",context:c||this,success:function(a){b(a.results)}})};djr.Backend.prototype.markovPlaylist=function(a,b,c,d){$.ajax({url:"/json/markov",data:{h:b.join(","),n:a},dataType:"json",type:"POST",context:d||this,success:function(a){c(a.results)}})};djr.Backend.prototype.randomPlaylist=function(a,b,c){$.ajax({url:"/json/random",data:{n:a},dataType:"json",type:"GET",context:c||this,success:function(a){b(a.results)}})};djr.PlaylistChunk=function(a,b){this.songs=a||[];this.title=b};djr.PlaylistChunk.prototype.hasSong=function(a){return this.songs.indexOf(a)>=0};djr.PlaylistChunk.prototype.removeSong=function(a){this.songs=$.grep(this.songs,function(b){return b!=a})};djr.controlButtons=function(a){return'<div class="ctlbox" style="display:none"><a id="'+a+'_remove" class="ctl_btn ctl_remove"> </a></div>'}; djr.PlaylistChunk.prototype.wrapHtml=function(a,b){return'<div id="chunk_'+a+'" class="chunk"><div class="chunk_ctl_wrap">'+djr.controlButtons("chunk_ctl_"+a)+'<a class="chunk_title">'+this.title+'</a></div><div class="chunk_inner">'+(b||"")+"</div></div>"};djr.Playlist=function(a){this.uuid=a||djr.generateRandomId();this.chunks=[];this.song_map={};this.chunk_map={};this.next_chunk_id=0}; djr.Playlist.prototype.allSongs=function(){var a=[],b,c;for(b=0;b<this.chunks.length;b++)for(c=0;c<this.chunk_map[this.chunks[b]].songs.length;c++)a.push(this.chunk_map[this.chunks[b]].songs[c]);return a};djr.Playlist.prototype.createUniqueChunk=function(a,b){var c=[],d;for(d=0;d<a.length;d++)this.song_map[a[d]]==null&&c.push(a[d]);return c.length>0?new djr.PlaylistChunk(c,b):null}; djr.Playlist.prototype.addChunk=function(a){djr.debug("adding chunk to playlist "+this.uuid);var b,c=this.next_chunk_id++;for(b=0;b<a.songs.length;b++)this.song_map[a.songs[b]]=c;this.chunk_map[c]=a;this.chunks.push(c);return c};djr.Playlist.prototype.getChunkSongs=function(a){return this.chunk_map[a].songs}; @@ -8,10 +9,14 @@ djr.Playlist.prototype.removeChunk=function(a){djr.debug("removing chunk "+a);va djr.Playlist.prototype.merge=function(){var a=[],b;for(b=0;b<this.chunks.length;b++)a.push(this.chunk_map[this.chunks[b]].title);a=a.join(" + ");b=new djr.Playlist;b.uuid=this.uuid;b.addChunk(new djr.PlaylistChunk(this.allSongs(),a));return b};djr.Playlist.prototype.getNextSong=function(a){var b=this.song_map[a],c=this.chunk_map[b].songs,b=this.chunks.indexOf(b),a=c.indexOf(a)+1;a>=c.length&&(a=0,b++,b>=this.chunks.length&&(b=0));return this.chunk_map[this.chunks[b]].songs[a]};djr.Player=function(a,b){this.backend=a;this.player=$(b);this.playlist=new djr.Playlist;this.old_songs=[];this.cur_song=null;this.player.jPlayer({swfPath:"/static/js",ready:function(){djr.debug("player ready")}});this.player.bind($.jPlayer.event.ended+".djr",function(){djr.state.player.nextSong()});this.player.bind($.jPlayer.event.error+".djr",function(){djr.state.player.reportError()})};djr.Player.prototype.hideAllChunks=function(){$(".chunk .chunk_inner").hide()}; djr.Player.prototype.removeChunk=function(a){this.playlist.removeChunk(a);this.savePlaylist();$("#chunk_"+a).remove()};djr.Player.prototype.removeSong=function(a){$("#song_"+a).remove();a=this.playlist.removeSong(a);this.savePlaylist();a>0&&$("#chunk_"+a).remove()};djr.Player.prototype.savePlaylist=function(){this.backend.savePlaylist(this.playlist.uuid,this.playlist.allSongs())};djr.Player.prototype.clearPlaylist=function(){this.playlist=new djr.Playlist;$("#playlistDiv").empty()}; djr.Player.prototype.mergePlaylistChunks=function(){this.playlist=this.playlist.merge();var a=[];$(".chunk .chunk_inner").each(function(){a.push($(this).html())});$("#playlistDiv").empty();var b=this.playlist.chunks[0];this.setChunkHtml(this.playlist.chunk_map[b],b,a.join(""))};djr.Player.prototype.search=function(a){var b=this;this.backend.search(a,function(c){var d=[];$.each(c,function(a,b){d.push(b.sha1)});d.length==0?djr.debug("No results found."):b.createChunk(d,a)})}; -djr.Player.prototype.extendCurrentPlaylist=function(){var a=this;this.backend.moreLikeThese(this.playlist.allSongs(),function(b){a.createChunk(b,"suggestions")})};djr.Player.prototype.createChunk=function(a,b){var c=this.playlist.createUniqueChunk(a,b);if(c){this.playlist.chunks.length>1&&this.mergePlaylistChunks();var d=this.playlist.addChunk(c);this.savePlaylist();this.backend.getHtmlForSongs(a,function(a){this.hideAllChunks();this.setChunkHtml(c,d,a)},this)}else djr.debug("All the results are already in the playlist")}; +djr.Player.prototype.lastPlaylist=function(a){var b=this,c="Last "+a+" Songs Uploaded";this.backend.lastPlaylist(a,function(a){a.length==0?djr.debug("No results found."):b.createChunk(a,c)})};djr.Player.prototype.randomPlaylist=function(a){var b=this,c=""+a+" Random Songs ";this.backend.randomPlaylist(a,function(a){a.length==0?djr.debug("No results found."):b.createChunk(a,c)})}; +djr.Player.prototype.mostPlayedPlaylist=function(a){var b=this,c=""+a+" Most Played Songs";this.backend.mostPlayedPlaylist(a,function(a){var e=[];$.each(a,function(a,b){e.push(b.sha1)});e.length==0?djr.debug("No results found."):b.createChunk(e,c)})};djr.Player.prototype.extendCurrentPlaylist=function(){var a=this;this.backend.moreLikeThese(this.playlist.allSongs(),function(b){a.createChunk(b,"suggestions")})}; +djr.Player.prototype.createChunk=function(a,b){var c=this.playlist.createUniqueChunk(a,b);if(c){this.playlist.chunks.length>1&&this.mergePlaylistChunks();var d=this.playlist.addChunk(c);this.savePlaylist();this.backend.getHtmlForSongs(a,function(a){this.hideAllChunks();this.setChunkHtml(c,d,a)},this)}else djr.debug("All the results are already in the playlist")}; djr.Player.prototype.setChunkHtml=function(a,b,c){a=a.wrapHtml(b,c);$("#playlistDiv").append(a);var d=this,e=$("#chunk_"+b);e.find(".song_a").click(function(){d.play($(this).attr("id").substr(5))});e.find(".album_a").click(function(){d.search('(album:"'+$(this).text()+'")')});e.find(".chunk_title").click(function(){e.find(".chunk_inner").toggle()});e.hover(function(){$(this).find(".chunk_ctl_wrap .ctlbox").show()},function(){$(this).find(".chunk_ctl_wrap .ctlbox").hide()});e.find(".chunk_ctl_wrap .ctlbox .ctl_remove").click(function(){djr.debug("removing chunk "+ b);d.removeChunk(b)});e.find(".chunk_inner .song").hover(function(){$(this).find(".ctlbox").show()},function(){$(this).find(".ctlbox").hide()});e.find(".chunk_inner .ctlbox .ctl_remove").click(function(){var a=$(this).parent().parent().attr("id").substr(5);d.removeSong(a)})}; djr.Player.prototype.play=function(a){djr.debug("play "+a);this.cur_song&&(this.old_songs.push(this.cur_song),this.old_songs.length>5&&this.old_songs.shift());this.cur_song=a;$(".song").removeClass("playing");$("#song_"+a).addClass("playing");this.player.jPlayer("setMedia",{mp3:"/dl/"+a}).jPlayer("play");var b=$("#song_"+a+" .artist").text(),c=$("#song_"+a+" .album").text();$("#jp_playlist_1").html($("#song_"+a+" .title").text()+"<br>"+b+"<br><small>"+c+"</small>");b="/album_image/"+escape(b)+"/"+ escape(c);$("#albumart_fs").attr("src",b);$("#albumart_fs").fullBg();$("#albumart_fs").show();this.backend.nowPlaying(a,this.old_songs)};djr.Player.prototype.nextSong=function(){this.play(this.playlist.getNextSong(this.cur_song))};djr.Player.prototype.streamCurrentPlaylist=function(){};djr.state={backend:null,player:null}; -djr.init=function(){djr.state.backend=new djr.Backend;djr.state.player=new djr.Player(djr.state.backend,"#djr_player");$("#playlistClearBtn").click(function(){djr.state.player.clearPlaylist()});$("#playlistStreamBtn").click(function(){djr.state.player.streamCurrentPlaylist()});$("#playlistExtendBtn").click(function(){djr.state.player.extendCurrentPlaylist()});$("#albumart_fs").load(function(){$(this).fullBg();$(this).show()})};djr.player=function(){return djr.state.player}; -djr.debug=function(a){$("#debug").append(a+"<br>")}; +djr.init=function(){djr.state.backend=new djr.Backend;djr.state.player=new djr.Player(djr.state.backend,"#djr_player");$("#playlistClearBtn").click(function(){djr.state.player.clearPlaylist()});$("#playlistStreamBtn").click(function(){djr.state.player.streamCurrentPlaylist()});$("#playlistExtendBtn").click(function(){djr.state.player.extendCurrentPlaylist()});$("#playlistLast25").click(function(){djr.state.player.lastPlaylist(25)});$("#playlistRandom").click(function(){djr.state.player.randomPlaylist(25)}); +$("#playlistMost").click(function(){djr.state.player.mostPlayedPlaylist(25)});$("#wikibtn").click(function(){var a=$("#song_"+djr.state.player.cur_song+" .artist").text();$("#wikipedia").is(":visible")==!1?a!=""&&(a=a.split(" ").join("+"),$("#wikipedia").show("slow"),$("#wikipedia").attr("src","http://en.m.wikipedia.org/w/index.php?search="+a)):$("#wikipedia").hide("slow")});$("#lastfmbtn").click(function(){var a=$("#song_"+djr.state.player.cur_song+" .title").text(),b=$("#song_"+djr.state.player.cur_song+ +" .artist").text();$("#lastfm").is(":visible")==!1?a!=""&&(a=a.split(" ").join("+"),b=b.split(" ").join("+"),$("#lastfm").show("slow"),$("#lastfm").attr("src","http://m.last.fm/search?q="+a+b)):$("#lastfm").hide("slow")});$("#lyricsbtn").click(function(){var a=$("#song_"+djr.state.player.cur_song+" .title").text();$("#lyrics").is(":visible")==!1?a!=""&&(a=a.split(" ").join("+"),$("#lyrics").show("slow"),$("#lyrics").attr("src","http://lyrics.wikia.com/index.php?search="+a+"&fulltext=0")):$("#lyrics").hide("slow")}); +$("#albumart_fs").load(function(){$(this).fullBg();$(this).show()})};djr.player=function(){return djr.state.player};djr.debug=function(a){$("#debug").append(a+"<br>")}; diff --git a/server/djrandom/frontend/static/js/djr/Makefile b/server/djrandom/frontend/static/js/djr/Makefile index fc3022305822eaa7bd74300e11eae147e1a27156..094d7250a7d6013277f8d83f5bb85f7c03be0ba7 100644 --- a/server/djrandom/frontend/static/js/djr/Makefile +++ b/server/djrandom/frontend/static/js/djr/Makefile @@ -1,5 +1,6 @@ -JSCOMPILER = java -jar ~/src/jscompiler/compiler.jar +JSCOMPILER_JAR = /usr/bin/compiler.jar +JSCOMPILER = java -jar $(JSCOMPILER_JAR) SOURCES = \ djr.js \ diff --git a/server/djrandom/frontend/static/js/djr/backend.js b/server/djrandom/frontend/static/js/djr/backend.js index 5c0f45c43bc288f1780e1314e403afb1095948f4..1921dc102228286e3ec43b720160cb30fdcc1854 100644 --- a/server/djrandom/frontend/static/js/djr/backend.js +++ b/server/djrandom/frontend/static/js/djr/backend.js @@ -1,6 +1,5 @@ // backend.js - /** * Backend API. * @@ -163,3 +162,77 @@ djr.Backend.prototype.nowPlaying = function(song, old_songs) { }); }; +/** + * Request N most played songs + * + * @param {integer} n Number of songs requested + * + */ +djr.Backend.prototype.mostPlayedPlaylist = function(num, callback ,ctx) { + $.ajax({url: '/json/most_played', + data: {n: num }, + dataType: 'json', + type: 'GET', + context: ctx || this, + success: function(data, status, jqxhr) { + callback(data.results); + } + }); +}; + +/** + * Request N most played songs + * + * @param {integer} n Number of songs requested + * + */ +djr.Backend.prototype.lastPlaylist = function(num, callback ,ctx) { + $.ajax({url: '/json/last_uploaded', + data: {n: num }, + dataType: 'json', + type: 'GET', + context: ctx || this, + success: function(data, status, jqxhr) { + callback(data.results); + } + }); +}; + +/** + * Return N pseudo-random songs based on the current playlist. + * + * @param {integer} n Number of songs requested + * @param {Array[string]} uuids SHA1 hashes of the current playlist + * + */ +djr.Backend.prototype.markovPlaylist = function(num, uuids, callback ,ctx) { + $.ajax({url: '/json/markov', + data: {'h': uuids.join(','), + 'n': num }, + dataType: 'json', + type: 'POST', + context: ctx || this, + success: function(data, status, jqxhr) { + callback(data.results); + } + }); +}; + +/** + * Return N completely random songs. + * + * @param {integer} n Number of songs requested + * + */ +djr.Backend.prototype.randomPlaylist = function(num, callback ,ctx) { + $.ajax({url: '/json/random', + data: {'n': num }, + dataType: 'json', + type: 'GET', + context: ctx || this, + success: function(data, status, jqxhr) { + callback(data.results); + } + }); +}; + diff --git a/server/djrandom/frontend/static/js/djr/player.js b/server/djrandom/frontend/static/js/djr/player.js index 2bdfa05574a734690e1f4b47d94bde0792e248c7..c6643c33919a6943aa402a2a715f0f097746183b 100644 --- a/server/djrandom/frontend/static/js/djr/player.js +++ b/server/djrandom/frontend/static/js/djr/player.js @@ -98,6 +98,57 @@ djr.Player.prototype.search = function(query) { }); }; +// Request N last uploaded songs + +djr.Player.prototype.lastPlaylist = function(num) { + var player = this; + var title = "Last " + num + " Songs Uploaded"; + this.backend.lastPlaylist(num, function(results) { + var songs = results; + if (songs.length == 0) { + djr.debug('No results found.'); + return; + } + // Create a chunk of unique new songs. + player.createChunk(songs, title); + }); + + +}; + +djr.Player.prototype.randomPlaylist = function(num) { + var player = this; + var title = "" + num + " Random Songs "; + this.backend.randomPlaylist(num, function(results) { + var songs = results; + if (songs.length == 0) { + djr.debug('No results found.'); + return; + } + // Create a chunk of unique new songs. + player.createChunk(songs, title); + }); + +}; + +djr.Player.prototype.mostPlayedPlaylist = function(num) { + var player = this; + var title = "" + num + " Most Played Songs"; + this.backend.mostPlayedPlaylist(num, function(results) { + var songs = []; + $.each(results, function(idx, item) { + songs.push(item.sha1); + }); + if (songs.length == 0) { + djr.debug('No results found.'); + return; + } + // Create a chunk of unique new songs. + player.createChunk(songs, title); + }); + +}; + // Extend the current playlist with suggestions. djr.Player.prototype.extendCurrentPlaylist = function() { var player = this; @@ -249,6 +300,58 @@ djr.init = function () { $('#playlistExtendBtn').click(function() { djr.state.player.extendCurrentPlaylist(); }); + $('#playlistLast25').click(function() { + djr.state.player.lastPlaylist(25); + }); + $('#playlistRandom').click(function() { + djr.state.player.randomPlaylist(25); + }); + $('#playlistMost').click(function() { + djr.state.player.mostPlayedPlaylist(25); + }); + + $('#wikibtn').click(function () { + var stitle = $('#song_' + djr.state.player.cur_song + ' .artist').text(); + if ( $('#wikipedia').is(':visible') == false ) { + if ( stitle != "" ) { + stitle = stitle.split(' ').join('+'); + $('#wikipedia').show('slow'); + //$('#wikipedia').attr("src", "/ext?url=" + escape("http://en.wikipedia.org/wiki/Special:Search?search=" + stitle + "&go=Go")); + $('#wikipedia').attr("src", "http://en.m.wikipedia.org/w/index.php?search=" + stitle); + } + } else { + $('#wikipedia').hide('slow') + } + }); + $('#lastfmbtn').click(function () { + var stitle = $('#song_' + djr.state.player.cur_song + ' .title').text(); + var sartist = $('#song_' + djr.state.player.cur_song + ' .artist').text(); + if ( $('#lastfm').is(':visible') == false ) { + if ( stitle != "" ) { + stitle = stitle.split(' ').join('+'); + sartist = sartist.split(' ').join('+'); + $('#lastfm').show('slow'); + //$('#lastfm').attr("src", "/ext?url=" + escape("http://www.lastfm.com/#&q=" + stitle + sartist)); + $('#lastfm').attr("src", "http://m.last.fm/search?q=" + stitle + sartist ); + } + } else { + $('#lastfm').hide('slow') + } + }); + $('#lyricsbtn').click(function () { + var stitle = $('#song_' + djr.state.player.cur_song + ' .title').text(); + if ( $('#lyrics').is(':visible') == false ) { + if ( stitle != "" ) { + stitle = stitle.split(' ').join('+'); + $('#lyrics').show('slow'); + //$('#lyrics').attr("src", "/ext?url=" + escape("http://www.lyrics.com/#&q=" + stitle)); + $('#lyrics').attr("src", "http://lyrics.wikia.com/index.php?search=" + stitle + "&fulltext=0" ); + } + } else { + $('#lyrics').hide('slow') + } + }); + // Set the album art image to auto-fullscreen on load. $('#albumart_fs').load(function() { diff --git a/server/djrandom/frontend/templates/_std_base.html b/server/djrandom/frontend/templates/_std_base.html new file mode 100644 index 0000000000000000000000000000000000000000..eef79cf7d907e9963a66c3b4f25464903642e1a8 --- /dev/null +++ b/server/djrandom/frontend/templates/_std_base.html @@ -0,0 +1,17 @@ +<!doctype html> +<html> + <head> + <title>DJ RANDOM :: {% block title %}{% endblock %}</title> + <link href="/static/favicon.ico" type="image/x-icon" rel="shortcut icon"> + <link rel="stylesheet" type="text/css" href="/static/css/login.css"> + </head> + <body> + + <div id="main"> + <div id="form"> + {% block main %} + {% endblock %} + </div> + </div> + </body> +</html> diff --git a/server/djrandom/frontend/templates/about.html b/server/djrandom/frontend/templates/about.html index 5c737152e9022947036f2c600a6e57e729f73682..aedc42e9eca58f0f9c1b254169ec5298eedadc17 100644 --- a/server/djrandom/frontend/templates/about.html +++ b/server/djrandom/frontend/templates/about.html @@ -1,14 +1,7 @@ -<!doctype html> -<html> - <head> - <title>DJ RANDOM :: About</title> - <link href="/static/favicon.ico" type="image/x-icon" rel="shortcut icon"> - <link rel="stylesheet" type="text/css" href="/static/css/login.css"> - </head> - <body> +{% extends "_std_base.html" %} +{% block title %}About{% endblock %} +{% block main %} - <div id="main"> - <div id="form"> <h3>DJ:Random</h3> <p> @@ -19,9 +12,4 @@ (<b>{{ used_gb }}</b> Gb). </p> - </div> - - </div> - - </body> -</html> +{% endblock %} diff --git a/server/djrandom/frontend/templates/index.html b/server/djrandom/frontend/templates/index.html index baab6bfc9e1adcf8ca63f03c9db239e2284bd905..e81f857ad16b17069112e52d3d066d364a79b371 100644 --- a/server/djrandom/frontend/templates/index.html +++ b/server/djrandom/frontend/templates/index.html @@ -73,6 +73,24 @@ DJ:Random <li></li> </ul> </div> + <div id="lyricsDiv" > + <a id="lyricsbtn">Lyrics</a> + <iframe id="lyrics" width="100%" height="300" style="display: none" > + <p>Your browser does not support iframes.</p> + </iframe> + </div> + <div id="wikiDiv" > + <a id="wikibtn">Wikipedia Author</a> + <iframe id="wikipedia" width="100%" height="300" style="display: none" > + <p>Your browser does not support iframes.</p> + </iframe> + </div> + <div id="lastfmDiv" > + <a id="lastfmbtn">LastFM Song Info</a> + <iframe id="lastfm" width="100%" height="300" style="display: none" > + <p>Your browser does not support iframes.</p> + </iframe> + </div> </div> </div> </div> @@ -85,6 +103,11 @@ DJ:Random </p> </form> + <div id="playlistButtons" > + <a id="playlistLast25">Last 25</a> + | <a id="playlistRandom">Random</a> + | <a id="playlistMost">Most Played</a> + </div> <div id="playlistDiv"> </div> diff --git a/server/djrandom/frontend/templates/redirect.html b/server/djrandom/frontend/templates/redirect.html new file mode 100644 index 0000000000000000000000000000000000000000..a99041f8d0dbd8c4c6da4f7b034d247a94388af9 --- /dev/null +++ b/server/djrandom/frontend/templates/redirect.html @@ -0,0 +1,13 @@ +{% extends "_std_base.html" %} +{% block title %}About{% endblock %} +{% block main %} + +<p> + Redirecting to <a href="{{ url | e }}">{{ url | e }}</a> ... +</p> + +<script type="text/javascript"> + window.location = '{{ url | e }}'; +</script> + +{% endblock %} diff --git a/server/djrandom/frontend/templates/user_details.html b/server/djrandom/frontend/templates/user_details.html index 5afee9535746294fe59a30edbbc477f02703055a..d12373a88a078e867b82b68865973f6415176f70 100644 --- a/server/djrandom/frontend/templates/user_details.html +++ b/server/djrandom/frontend/templates/user_details.html @@ -1,14 +1,6 @@ -<!doctype html> -<html> - <head> - <title>DJ RANDOM</title> - <link href="/static/favicon.ico" type="image/x-icon" rel="shortcut icon"> - <link rel="stylesheet" type="text/css" href="/static/css/login.css"> - </head> - <body> - - <div id="main"> - <div id="form"> +{% extends "_std_base.html" %} +{% block title %}User Details{% endblock %} +{% block main %} {%- for msg in get_flashed_messages() -%} <p class="flash">{{ msg | e }}</p> @@ -53,7 +45,4 @@ </div> - </div> - - </body> -</html> +{% endblock %} diff --git a/server/djrandom/frontend/templates/user_invite.html b/server/djrandom/frontend/templates/user_invite.html index 2cc3afa87234d1a98592291b67108262b56a2a47..ee7625b4e39ef270fb1db98e02efb9abb7da611b 100644 --- a/server/djrandom/frontend/templates/user_invite.html +++ b/server/djrandom/frontend/templates/user_invite.html @@ -1,14 +1,7 @@ -<!doctype html> -<html> - <head> - <title>DJ RANDOM</title> - <link href="/static/favicon.ico" type="image/x-icon" rel="shortcut icon"> - <link rel="stylesheet" type="text/css" href="/static/css/login.css"> - </head> - <body> +{% extends "_std_base.html" %} +{% block title %}Invites{% endblock %} +{% block main %} - <div id="main"> - <div id="form"> <form action="/user/invite" method="post"> {{ form.hidden_tag() | safe }} @@ -27,9 +20,5 @@ <input type="submit" class="f_submit" value=" Invite "> </p> </form> - </div> - </div> - - </body> -</html> +{% endblock %} diff --git a/server/djrandom/frontend/views.py b/server/djrandom/frontend/views.py index 662b70170e82ddf89c282faf4c014075a084dbcc..ec792dd7e8b7defdc867fa880aefc95ea0d5c09c 100644 --- a/server/djrandom/frontend/views.py +++ b/server/djrandom/frontend/views.py @@ -52,6 +52,15 @@ def homepage(): return render_template('index.html', user=user) +@app.route('/ext') +@require_auth +def redirect_to_external_url(): + url = request.args.get('url') + if not url: + abort(400) + return render_template('redirect.html', url=url) + + def fileiter(path, pos, end): with open(path, 'r') as fd: fd.seek(pos) diff --git a/server/djrandom/model/markov.py b/server/djrandom/model/markov.py index ac2f7da58ac770993fdb9dee8309e16d435653bf..11350ea8c193e92ab9af7cb3af7f0d5bb328d797 100644 --- a/server/djrandom/model/markov.py +++ b/server/djrandom/model/markov.py @@ -78,6 +78,8 @@ class MarkovModel(object): for off, value in self._norm_map[prev_n]: if off > r: return self._i2hash[value] + # Can't find anything, get a random song instead. + return random.choice(self._ishash()) def generate_sequence(self, prev, n, count): if len(prev) < n: diff --git a/server/djrandom/model/mp3.py b/server/djrandom/model/mp3.py index 12a5b539d3364c812c325c54aecf73aba93d50fa..68657c7686cea86ead20af9d0bcbf72708937b0f 100644 --- a/server/djrandom/model/mp3.py +++ b/server/djrandom/model/mp3.py @@ -1,6 +1,8 @@ +import random +from sqlalchemy.orm import deferred from sqlalchemy import * from datetime import datetime, timedelta -from djrandom.database import Base +from djrandom.database import Base, Session class MP3(Base): @@ -22,6 +24,7 @@ class MP3(Base): genre = Column(Unicode(64)) uploaded_at = Column(DateTime()) play_count = Column(Integer(), default=0) + echoprint_fp = deferred(Column(Text())) def __init__(self, **kw): for k, v in kw.items(): @@ -36,6 +39,23 @@ class MP3(Base): 'size': self.size, 'uploaded_at': self.uploaded_at} + @classmethod + def last_uploaded(cls, n=10): + """Return the N last uploaded songs.""" + return cls.query.filter_by(ready=True).order_by( + desc(cls.uploaded_at)).limit(n) + + @classmethod + def get_random_songs(cls, n=10): + """Return N completely random songs.""" + results = [] + num_songs = cls.query.filter_by(ready=True).count() + for idx in xrange(n): + song = cls.query.filter_by(ready=True).limit(1).offset( + random.randint(0, num_songs - 1)).one() + results.append(song) + return results + class PlayLog(Base): @@ -76,6 +96,7 @@ class SearchLog(Base): __tablename__ = 'searchlog' + id = Column(Integer(), primary_key=True) userid = Column(String(40), index=True) query = Column(String(256)) stamp = Column(DateTime()) diff --git a/server/setup.py b/server/setup.py index 56ad7c33bcec2b2e5935d844ce7ebe9d3d787a6e..4f8a61aa48a58b5e608dfbf7d297bfa9cdfa7e6d 100644 --- a/server/setup.py +++ b/server/setup.py @@ -9,7 +9,7 @@ setup( author="ale", author_email="ale@incal.net", url="http://git.autistici.org/git/djrandom.git", - install_requires=["Flask", "Flask-WTF", "gevent", "eyeD3"], + install_requires=["Flask", "Flask-WTF", "gevent", "eyeD3", "solrpy"], setup_requires=[], zip_safe=False, packages=find_packages(), @@ -17,8 +17,11 @@ setup( "console_scripts": [ "djrandom-receiver = djrandom.receiver.receiver:main", "djrandom-scanner = djrandom.scanner.scanner:main", + "djrandom-fingerprinter = djrandom.fingerprint.fingerprint:main", + "djrandom-dedup = djrandom.fingerprint.dedup:main", "djrandom-streamer = djrandom.stream.stream:main", "djrandom-frontend = djrandom.frontend.frontend:main", + "djrandom-update-markov = djrandom.model.markov:main", ], }, )