This commit is contained in:
nikolark committed 2016-04-02 15:11:03 +02:00
1 parent 0df0922867
commit 3a3874a8be
30 files changed
+305 -9318

No files matched your search

+6 -6
View File
@@ -1,16 +1,16 @@
# Install # Install
apt install python-virtualenv libmysqlclient-dev apt install python-virtualenv libmysqlclient-dev
virtualenv venv pyvenv venv
. venv/bin/activate . venv/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
python manage.py migrate python manage.py migrate
python manage.py runserver python manage.py loaddata testdata
* You will also need a LastFM account and a API account from http://www.last.fm/api/account * You will also need a LastFM account and a API account from http://www.last.fm/api/account
* Create a local\_settings.py and set your own LASTFM\_APIKEY and LASTFM\_API\_SECRET * Create a local\_settings.py and set your own LASTFM\_APIKEY and LASTFM\_API\_SECRET
# Usage # Usage
* Create an admin user: python manage.py createsuperuser - Create an admin user: python manage.py createsuperuser
* Goto /admin, login, then add a LastFMUser and make it active. - Goto /admin, login, then add a LastFMUser and make it active.
* Setup your music player (f.ex Spotify) to scrobble to that LastFM account. - Setup your music player (f.ex Spotify) to scrobble to that LastFM account.
* Goto / and you should be set :=) - Goto / and you should be set :=)
+55
View File
@@ -0,0 +1,55 @@
from django.utils import timezone
from rest_framework.response import Response
from rest_framework.views import APIView
from django.contrib.humanize.templatetags.humanize import naturaltime
from dateutil import parser
from oliver_screen import lastfm, utils
class LastTrackView(APIView):
def _get_playback_date(self, playback_date_string):
playback_date = parser.parse(playback_date_string)
return timezone.make_aware(playback_date, timezone.UTC()) # Last.fm API is in UTC
def _time_since(self, playback_date_string):
playback_date = self._get_playback_date(playback_date_string)
formatted = naturaltime(playback_date)
formatted = formatted.replace(u'\xa0', u' ') # remove non-breaking space (in latin1)
return formatted
def get(self, request, format=None):
user = lastfm.get_user()
playedtracks = user.get_recent_tracks(limit=2)
track_data = {
'artist': playedtracks[0].track.get_artist().get_name(),
'title': playedtracks[0].track.get_title(),
'playback_date': self._get_playback_date(playedtracks[0].playback_date),
'time_since': self._time_since(playedtracks[0].playback_date)
}
return Response({'track': track_data})
class CurrentTrackView(APIView):
def get(self, request, format=None):
user = lastfm.get_user()
track = user.get_now_playing()
if track is None:
# No response from LastFM?
return Response({'track': utils.get_current_from_cache()})
artist = track.get_artist()
video = utils.get_youtubevideo(artist.get_name(), track.get_title())
image = artist.get_cover_image(size=4)
now_playing = {
'artist': artist.get_name(),
'title': track.get_title(),
'image': image,
'video': video
}
utils.cache_current(now_playing)
return Response({'track': now_playing})
+10
View File
@@ -0,0 +1,10 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class OliveScreenConfig(AppConfig):
name = 'oliver_screen'
verbose_name = _('Oliver Screen')
def ready(self):
pass
File renamed without changes.
+13
View File
@@ -0,0 +1,13 @@
import pylast
from django.conf import settings
from oliver_screen.models import LastFMUser
def get_user():
network = pylast.LastFMNetwork(api_key=settings.LASTFM_API_KEY, api_secret=settings.LASTFM_API_SECRET)
lastfmusers = LastFMUser.objects.filter(active=True)
if len(lastfmusers) != 1:
# default
return network.get_user(settings.LASTFM_DEFAULT_USERNAME)
return network.get_user(lastfmusers[0].name)
+3 -3
View File
@@ -7,7 +7,7 @@ class CurrentTrack(models.Model):
image = models.CharField(max_length=200) image = models.CharField(max_length=200)
retries = models.IntegerField() retries = models.IntegerField()
def __unicode__(self): def __str__(self):
return self.title + " - " + self.artist return self.title + " - " + self.artist
@@ -15,7 +15,7 @@ class LastFMUser(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
active = models.BooleanField(default=True) active = models.BooleanField(default=True)
def __unicode__(self): def __str__(self):
if self.active: if self.active:
return self.name + ": active" return self.name + ": active"
return self.name + ": NOT active" return self.name + ": NOT active"
@@ -28,7 +28,7 @@ class YouTubeVideo(models.Model):
start = models.IntegerField() start = models.IntegerField()
active = models.BooleanField(default=True) active = models.BooleanField(default=True)
def __unicode__(self): def __str__(self):
if self.active: if self.active:
return self.artist + " - " + self.title + ": active" return self.artist + " - " + self.title + ": active"
return self.artist + " - " + self.title + " : NOT active" return self.artist + " - " + self.title + " : NOT active"
File renamed without changes.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 673 B

File diff suppressed because it is too large. Load diff
-18
View File
@@ -1,18 +0,0 @@
/*!
* jQuery JavaScript Library v1.6.1
* http://jquery.com/
*
* Copyright 2011, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2011, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Thu May 12 15:04:36 2011 -0400
*/
(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$Line truncated
)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nLine truncated
b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.displayLine truncated
-138
View File
@@ -1,138 +0,0 @@
/**
* jQuery.timers - Timer abstractions for jQuery
* Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
* Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
* Date: 2009/10/16
*
* @author Blair Mitchelmore
* @version 1.2
*
**/
jQuery.fn.extend({
everyTime: function(interval, label, fn, times) {
return this.each(function() {
jQuery.timer.add(this, interval, label, fn, times);
});
},
oneTime: function(interval, label, fn) {
return this.each(function() {
jQuery.timer.add(this, interval, label, fn, 1);
});
},
stopTime: function(label, fn) {
return this.each(function() {
jQuery.timer.remove(this, label, fn);
});
}
});
jQuery.extend({
timer: {
global: [],
guid: 1,
dataKey: "jQuery.timer",
regex: /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/,
powers: {
// Yeah this is major overkill...
'ms': 1,
'cs': 10,
'ds': 100,
's': 1000,
'das': 10000,
'hs': 100000,
'ks': 1000000
},
timeParse: function(value) {
if (value == undefined || value == null)
return null;
var result = this.regex.exec(jQuery.trim(value.toString()));
if (result[2]) {
var num = parseFloat(result[1]);
var mult = this.powers[result[2]] || 1;
return num * mult;
} else {
return value;
}
},
add: function(element, interval, label, fn, times) {
var counter = 0;
if (jQuery.isFunction(label)) {
if (!times)
times = fn;
fn = label;
label = interval;
}
interval = jQuery.timer.timeParse(interval);
if (typeof interval != 'number' || isNaN(interval) || interval < 0)
return;
if (typeof times != 'number' || isNaN(times) || times < 0)
times = 0;
times = times || 0;
var timers = jQuery.data(element, this.dataKey) || jQuery.data(element, this.dataKey, {});
if (!timers[label])
timers[label] = {};
fn.timerID = fn.timerID || this.guid++;
var handler = function() {
if ((++counter > times && times !== 0) || fn.call(element, counter) === false)
jQuery.timer.remove(element, label, fn);
};
handler.timerID = fn.timerID;
if (!timers[label][fn.timerID])
timers[label][fn.timerID] = window.setInterval(handler,interval);
this.global.push( element );
},
remove: function(element, label, fn) {
var timers = jQuery.data(element, this.dataKey), ret;
if ( timers ) {
if (!label) {
for ( label in timers )
this.remove(element, label, fn);
} else if ( timers[label] ) {
if ( fn ) {
if ( fn.timerID ) {
window.clearInterval(timers[label][fn.timerID]);
delete timers[label][fn.timerID];
}
} else {
for ( var fn in timers[label] ) {
window.clearInterval(timers[label][fn]);
delete timers[label][fn];
}
}
for ( ret in timers[label] ) break;
if ( !ret ) {
ret = null;
delete timers[label];
}
}
for ( ret in timers ) break;
if ( !ret )
jQuery.removeData(element, this.dataKey);
}
}
}
});
jQuery(window).bind("unload", function() {
jQuery.each(jQuery.timer.global, function(index, item) {
jQuery.timer.remove(item);
});
});
+143
View File
@@ -0,0 +1,143 @@
function format_last_track(track) {
return track.artist + ' - ' + track.title + ' <span class="small">' + track.time_since + ' ago.</span><br />';
}
function format_current_track(track) {
if($.isEmptyObject(track)) {
return '<img src="'+ staticFiles.notrack +'" /><br />';
}
var html = '';
if(track.image) {
html += '<img src="' + track.image +'" class="now_playing" /><br />';
}
html += '<img src="'+ staticFiles.spectrumAnalyzer +'" />' + track.artist + ' - ' + track.title +' ♪♫<br />';
return html;
}
/* Update track and now playing */
function get_last_track() {
$.getJSON(urls.trackLast, function(data) {
$( '#last_track' ).html(format_last_track(data.track));
});
}
function get_now_playing() {
$.getJSON(urls.trackCurrent, function(data) {
$( '#now_playing' ).html(format_current_track(data.track));
if( is_tetris_song() ) {
play_tetris();
} else {
dont_play_tetris();
}
});
}
/***********
* YOUTUBE *
***********/
function other_video_playing(video) {
var now_playing_pattern = /video.videoId/;
return ! now_playing_pattern.test( ytplayer.getVideoUrl() );
}
function playVideo(video) {
if(ytplayer) {
// start the video (if we have'nt allready)
if( other_video_playing(video) ) {
// show the player
$("#player").show();
ytplayer.loadVideoById(video.videoId, video.start , "hd720");
}
}
}
function showOrHidePlayer(track) {
if(track.artist == '') {
$("#player").delay(1000).hide();
} else {
if( track.video != '') {
$("#player").show();
playVideo(track.video);
}
}
}
function onYouTubePlayerAPIReady() {
/* Initial player */
new YT.Player('player', {
width: '1200',
height: '675',
videoId: 'dQw4w9WgXcQ', // Mr. Astley?
playerVars: {
controls: 0, // no controls
iv_load_policy: 3, // no annotations
start: 215 // offset into video (last 2 seconds)
},
/* Attach to these events. */
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
/* The API will call this function when the video player is ready. */
function onPlayerReady(event) {
ytplayer = event.target;
event.target.playVideo();
event.target.mute();
event.target.setPlaybackQuality("hd720");
}
/* The API calls this function when the player's state changes. */
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.ENDED) {
/* Could do something on this state or others... */
}
}
function createYoutubePlayer() {
/* Loads the YouTube IFrame Player API code asynchronously. */
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
/*********
* Start *
*********/
/* Creates an <iframe> (and YouTube player) after the API code downloads. */
var ytplayer;
var urls = {
trackLast: '/api/track/last/', // {% url 'track-last' %}
trackCurrent: '/api/track/current/' // {% url 'track-current' %}
};
/* Initial update */
$( document ).ready( function() {
get_last_track();
get_now_playing();
// createYoutubePlayer();
});
/* Start timers updating a screen resource. */
setInterval(get_last_track, 10000);
setInterval(get_now_playing, 10000);
/* Ajax spinner */
$( document )
.ajaxStart(function() {
$( '#spinner' ).show();
})
.ajaxStop( function() {
$( '#spinner' ).hide();
});
@@ -89,7 +89,7 @@ function createABrick()
/* Randomly choose a brick image and assign it to the newly created element */ /* Randomly choose a brick image and assign it to the newly created element */
/* FIXME: this url should be dynamic */ /* FIXME: this url should be dynamic */
image.src = '/static/brick' + randomInteger(1, 5) + '.png'; image.src = '/static/images/brick' + randomInteger(1, 5) + '.png';
brickDiv.style.top = "-150px"; brickDiv.style.top = "-150px";
File renamed without changes.
+12
View File
@@ -0,0 +1,12 @@
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<title>Current track</title>
{% block head %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
+25 -94
View File
@@ -1,98 +1,29 @@
{% extends "public/base.html" %}
{% load staticfiles %} {% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<title>Current track</title>
<link rel="stylesheet" type="text/css" href="{% static 'screen.css' %}" />
<script type="text/javascript" src="{% static 'jquery-1.6.1.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery.timers-1.2.js' %}"></script>
<script type="text/javascript" src="{% static 'falling-pieces.js' %}"></script>
<script type="text/javascript">
/* Update track and now playing */
var get_last_track = function() {
$( '#last_track' ).load('{% url 'last-track' %}');
};
var get_now_playing = function() {
$( '#now_playing' ).load('{% url 'now-playing' %}', function() {
if( is_tetris_song() ) {
play_tetris();
} else {
dont_play_tetris();
}
});
};
/* Initial update */
$( document ).ready( get_last_track );
$( document ).ready( get_now_playing );
/* Start timers updating a screen resource. */
$( document ).everyTime('10s', "last_track", get_last_track );
$( document ).everyTime('10s', "now_playing", get_now_playing );
/* Ajax spinner */ {% block head %}
$( document ) <link rel="stylesheet" type="text/css" href="{% static 'styles/screen.css' %}" />
.ajaxStart(function() { <script type="text/javascript" src="https://code.jquery.com/jquery-2.2.2.min.js"></script>
$( '#spinner' ).show(); <script type="text/javascript" src="{% static 'scripts/falling-pieces.js' %}"></script>
}) <script type="text/javascript" src="{% static 'scripts/app.js' %}"></script>
.ajaxStop( function() { <script>
$( '#spinner' ).hide(); var staticFiles = {
}); notrack: '{% static 'images/no_music_makes_me_sick_by_sakgaa.jpg' %}',
spectrumAnalyzer: '{% static 'images/spectrum-analyzer.gif' %}'
};
</script>
{% endblock %}
/* Loads the YouTube IFrame Player API code asynchronously. */ {% block body %}
var tag = document.createElement('script'); <div id="spinner"><img src="{% static 'images/spinner.gif' %}"/></div>
tag.src = "https://www.youtube.com/player_api"; <div id="container">
var firstScriptTag = document.getElementsByTagName('script')[0]; <div id="content">
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); <div id="player"></div>
<div id="now_playing"></div>
/* Creates an <iframe> (and YouTube player) after the API code downloads. */ <div id="last_track"></div>
var ytplayer;
function onYouTubePlayerAPIReady() {
/* Initial player */
new YT.Player('player', {
width: '1200',
height: '675',
videoId: 'dQw4w9WgXcQ', // Mr. Astley?
playerVars: {
controls: 0, // no controls
iv_load_policy: 3, // no annotations
start: 215 // offset into video (last 2 seconds)
},
/* Attach to these events. */
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
/* The API will call this function when the video player is ready. */
function onPlayerReady(event) {
ytplayer = event.target;
event.target.playVideo();
event.target.mute();
event.target.setPlaybackQuality("hd720");
}
/* The API calls this function when the player's state changes. */
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.ENDED) {
/* Could do something on this state or others... */
}
}
</script>
</head>
<body>
<div id="spinner"><img src="{% static 'spinner.gif' %}"/></div>
<div id="container">
<div id="content">
<div id="player"></div>
<div id="now_playing"></div>
<div id="last_track"></div>
</div>
</div> </div>
<div id="gameContainer"> </div>
<div id="brickContainer"></div> <div id="gameContainer">
</div> <div id="brickContainer"></div>
</div>
</body> {% endblock %}
</html>
@@ -1,38 +0,0 @@
{% load staticfiles %}
{% if now_playing %}
{% if now_playing.video %}
<script type="text/javascript">
function other_video_playing() {
var now_playing_pattern = /{{ now_playing.video.videoId }}/;
return ! now_playing_pattern.test( ytplayer.getVideoUrl() );
}
function play() {
if(ytplayer) {
// start the video (if we have'nt allready)
if( other_video_playing() ) {
// show the player
$("#player").show();
ytplayer.loadVideoById("{{ now_playing.video.videoId }}", {{ now_playing.video.start }}, "hd720");
}
}
}
play();
</script>
{% else %}
<script type="text/javascript">
$("#player").delay(1000).hide();
</script>
{% if now_playing.image %}
<img src="{{ now_playing.image}}" class="now_playing" /><br />
{% endif %}
{% endif %}
<img src="{% static 'spectrum-analyzer.gif' %}" /> {{ now_playing.artist}} - {{ now_playing.title }} ♪♫<br />
{% else %}
<script type="text/javascript">
$("#player").delay(1000).hide();
</script>
<img src="{% static 'no_music_makes_me_sick_by_sakgaa.jpg' %}" /><br />
:[
{% endif %}
@@ -1,3 +0,0 @@
{% if played %}
{{ played.artist }} - {{ played.title }} <span class="small">{{ played.playback_date|timesince:played.utcnow }} ago.</span><br />
{% endif %}
+10 -6
View File
@@ -1,7 +1,11 @@
from django.conf.urls import patterns, url from django.conf.urls import url
urlpatterns = patterns( from oliver_screen.api import LastTrackView, CurrentTrackView
'oliver_screen.views', from oliver_screen.views import IndexView
url(r'^last/$', 'get_last_track', name='last-track'),
url(r'^now_playing/$', 'get_now_playing', name='now-playing'), urlpatterns = [
) url(r'^$', IndexView.as_view(), name='index'),
# API
url(r'^api/track/last/', LastTrackView.as_view(), name='track-last'),
url(r'^api/track/current/', CurrentTrackView.as_view(), name='track-current')
]
+3 -16
View File
@@ -1,17 +1,4 @@
import pylast from oliver_screen.models import CurrentTrack, YouTubeVideo
from django.conf import settings
from oliver_screen.models import CurrentTrack, LastFMUser, YouTubeVideo
def get_lastfmuser():
network = pylast.LastFMNetwork(api_key=settings.LASTFM_API_KEY, api_secret=settings.LASTFM_API_SECRET)
lastfmusers = LastFMUser.objects.filter(active=True)
if len(lastfmusers) != 1:
# default
return network.get_user(settings.LASTFM_DEFAULT_USERNAME)
return network.get_user(lastfmusers[0].name)
def get_youtubevideo(artist, title): def get_youtubevideo(artist, title):
@@ -27,7 +14,7 @@ def cache_current(now_playing):
# Fetch the one CurrentTrack entry in the db and update it with the current track. # Fetch the one CurrentTrack entry in the db and update it with the current track.
try: try:
current = CurrentTrack.objects.get(pk=1) current = CurrentTrack.objects.get(pk=1)
except: except CurrentTrack.DoesNotExist:
# update # update
current = CurrentTrack(artist="", title="", image="", retries=0) current = CurrentTrack(artist="", title="", image="", retries=0)
@@ -41,7 +28,7 @@ def cache_current(now_playing):
def get_current_from_cache(): def get_current_from_cache():
try: try:
current = CurrentTrack.objects.get(pk=1) current = CurrentTrack.objects.get(pk=1)
except: except CurrentTrack.DoesNotExist:
current = CurrentTrack(artist="", title="", image="", retries=0) current = CurrentTrack(artist="", title="", image="", retries=0)
current.save() current.save()
+10 -50
View File
@@ -1,63 +1,23 @@
from datetime import datetime from django.http import JsonResponse
from django.shortcuts import render from django.views.generic import TemplateView
from oliver_screen import utils
def index(request): class IndexView(TemplateView):
return render(request, 'public/index.html') template_name = 'public/index.html'
def get_last_track(request):
user = utils.get_lastfmuser()
playedtracks = user.get_recent_tracks(limit=2)
playback_date = datetime.strptime(playedtracks[0].playback_date, "%d %b %Y, %H:%M")
played = {
'artist': playedtracks[0].track.get_artist().get_name(),
'title': playedtracks[0].track.get_title(),
'playback_date': playback_date,
'utcnow': datetime.utcnow() # provide utcnow since lastfm servers are UTC
}
return render(request, "public/tracks.html", {'played': played})
def get_now_playing(request):
user = utils.get_lastfmuser()
track = user.get_now_playing()
if track is not None:
artist = track.get_artist()
video = utils.get_youtubevideo(artist.get_name(), track.get_title())
image = artist.get_cover_image(size=4)
if "The Shins" in artist.get_name():
image = 'http://nikolark.at.neuf.no/the_shins.jpg'
now_playing = {
'artist': artist.get_name(),
'title': track.get_title(),
'image': image,
'video': video
}
utils.cache_current(now_playing)
else:
# No response from LastFM db?
now_playing = utils.get_current_from_cache()
return render(request, "public/now_playing.html", {'now_playing': now_playing})
# Mock views # Mock views
def dummy_get_last_track(request): def dummy_get_last_track(request):
played = {'artist': 'The Album Leaf', 'title': 'Another Day (Revised)'} track = {'artist': 'The Album Leaf', 'title': 'Another Day (Revised)', 'time_since': '10 seconds'}
return JsonResponse(track)
return render(request, "public/tracks.html", {'played': played})
def dummy_now_playing(request): def dummy_now_playing(request):
now_playing = { track = {
'artist': 'The Album Leaf', 'artist': 'The Album Leaf',
'title': 'Over The Pond', 'title': 'Over The Pond',
'image': 'http://userserve-ak.last.fm/serve/_/2857325/The+Album+Leaf+1155442304.jpg' 'image': 'http://userserve-ak.last.fm/serve/_/2857325/The+Album+Leaf+1155442304.jpg',
"video": ''
} }
return render(request, "public/now_playing.html", {'now_playing': now_playing}) return JsonResponse({'track': track})
+3 -2
View File
@@ -17,16 +17,17 @@ TEMPLATE_DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
'flat',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'rest_framework',
) )
INSTALLED_APPS += ( INSTALLED_APPS += (
+6 -6
View File
@@ -1,9 +1,9 @@
from django.conf.urls import patterns, include, url from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
urlpatterns = patterns( from oliver_screen import urls as oliver_screen_urls
'',
url(r'^$', 'oliver_screen.views.index', name='index'), urlpatterns = [
url(r'', include('oliver_screen.urls')), url(r'', include(oliver_screen_urls)),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
) ]
+2 -1
View File
@@ -8,7 +8,8 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
""" """
import os import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oliversite.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oliversite.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application() application = get_wsgi_application()
+3
View File
@@ -1,3 +1,6 @@
Django>=1.8,<1.9 Django>=1.8,<1.9
djangorestframework==3.3.3
django-flat-theme==1.1.3
pylast==1.4.2 pylast==1.4.2
mysqlclient==1.3.6 mysqlclient==1.3.6
python-dateutil==2.5.2