|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# -*- coding: Latin-1 -*-
|
|
|
|
|
#
|
|
|
|
|
# Author: Andr<64> Kelpe fs111 at web dot de
|
|
|
|
|
# : Jonas Drewsen kde at xspect dot dk
|
|
|
|
|
# : Peter Ndikuwera pndiku at gmail dot com
|
|
|
|
|
#
|
|
|
|
|
# License: GPL
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
import user
|
|
|
|
|
import os
|
|
|
|
|
from xml.dom import minidom
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
import string
|
|
|
|
|
|
|
|
|
|
import Globals
|
|
|
|
|
|
|
|
|
|
# the current.xml file
|
|
|
|
|
PLAYLISTFILE = "%s/.trinity/share/apps/amarok/current.xml"%(user.home)
|
|
|
|
|
|
|
|
|
|
# the fields to be shown via http
|
|
|
|
|
FIELDS = ("TrackNo", "Title", "Artist", "Album", "Length", "Rating")
|
|
|
|
|
|
|
|
|
|
class Track(object):
|
|
|
|
|
"""Class that holds the information of one track in the current playlist"""
|
|
|
|
|
__Q_SLOTS__ = FIELDS
|
|
|
|
|
|
|
|
|
|
max_field_value_lengths = [(0,"")] * len(FIELDS)
|
|
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
for key,value in kwargs.iteritems():
|
|
|
|
|
setattr(self, key, value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def toRow(self, style='', id=None, trackno=None, reqid=None, sesid = None):
|
|
|
|
|
"""Returns the a html-table-row with member values of this class"""
|
|
|
|
|
|
|
|
|
|
trno = str(trackno)
|
|
|
|
|
astart = "<a "
|
|
|
|
|
if id:
|
|
|
|
|
astart += "name='nowplaying' "
|
|
|
|
|
# append "#nowplaying" if we want to jump to current song.
|
|
|
|
|
astart += ("class='track' href='javascript:dolink(\"action=goto&value="
|
|
|
|
|
+ trno + "&"
|
|
|
|
|
+ reqid + "&"
|
|
|
|
|
+ sesid + "\");'>")
|
|
|
|
|
|
|
|
|
|
aend = "</a>"
|
|
|
|
|
|
|
|
|
|
tmp = [ '<td>' + astart + i + aend +'</td>' for i in [getattr(self,f) for f in self.__Q_SLOTS__ ] ]
|
|
|
|
|
|
|
|
|
|
index = 0
|
|
|
|
|
# for f in self.__Q_SLOTS__ :
|
|
|
|
|
# print string.strip(f)
|
|
|
|
|
|
|
|
|
|
for i in [getattr(self,f) for f in self.__Q_SLOTS__ ]:
|
|
|
|
|
if len(string.strip(i)) > Track.max_field_value_lengths[index][0]:
|
|
|
|
|
Track.max_field_value_lengths[index] = (len(string.strip(i)),i)
|
|
|
|
|
index += 1
|
|
|
|
|
|
|
|
|
|
tr_style = ''
|
|
|
|
|
tr_id = ''
|
|
|
|
|
|
|
|
|
|
if style:
|
|
|
|
|
tr_style='class="%s"'%(style)
|
|
|
|
|
|
|
|
|
|
if trackno == 0:
|
|
|
|
|
tr_id = 'id="trackone"'
|
|
|
|
|
|
|
|
|
|
if id:
|
|
|
|
|
tr_id = 'id="%s"'%(id)
|
|
|
|
|
|
|
|
|
|
return '<tr %s %s>%s</tr>'%(tr_style, tr_id, ''.join(tmp))
|
|
|
|
|
|
|
|
|
|
class Playlist:
|
|
|
|
|
"""The Playlist class represents the Playlist as one object. It holds all
|
|
|
|
|
needed information and does most of the work"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
"""The Constructor takes no arguments."""
|
|
|
|
|
self.tracks = []
|
|
|
|
|
self.encoding = "UTF-8"
|
|
|
|
|
#"ISO-8859-15"
|
|
|
|
|
self.fullPage = ""
|
|
|
|
|
self.templateFilename = "template.thtml"
|
|
|
|
|
self.templateLastChanged = -1
|
|
|
|
|
self.templateLastChanged = self._loadHtmlTemplate()
|
|
|
|
|
|
|
|
|
|
def _loadHtmlTemplate(self):
|
|
|
|
|
"""Loads the global string template variables that are used for
|
|
|
|
|
rendering the page. The template can be changed by setting
|
|
|
|
|
self.templateFilename to a template file."""
|
|
|
|
|
ep = Globals.EXEC_PATH
|
|
|
|
|
st = os.stat(ep + "/" + self.templateFilename)[8]
|
|
|
|
|
|
|
|
|
|
if self.templateLastChanged != st:
|
|
|
|
|
tmp = open(ep + "/" + self.templateFilename).read()
|
|
|
|
|
a = compile(tmp, "<string>", 'exec')
|
|
|
|
|
eval(a)
|
|
|
|
|
return st
|
|
|
|
|
|
|
|
|
|
def toHtml(self, status):
|
|
|
|
|
"""Returns a html representation of the whole Playlist"""
|
|
|
|
|
self.templateLastChanged = self._loadHtmlTemplate()
|
|
|
|
|
self.sync()
|
|
|
|
|
self.tracks = []
|
|
|
|
|
self.mtime = self._getMtime()
|
|
|
|
|
self._buildDoc()
|
|
|
|
|
self._setFullPage(status)
|
|
|
|
|
return self.fullPage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _createButton(self, name, action, reqid, sesid):
|
|
|
|
|
"""Return a button to be used as an action"""
|
|
|
|
|
return ("<a href='javascript:dolink(\"action="
|
|
|
|
|
+ action + reqid + sesid + "\");'><img src='player_" + name + ".png'></a>")
|
|
|
|
|
|
|
|
|
|
def _createVolume(self, vol_val, reqid, sesid):
|
|
|
|
|
"""Return a HTML volume seletor."""
|
|
|
|
|
|
|
|
|
|
volume = '<table width="100%" class="volume">'
|
|
|
|
|
volume += "<tr>";
|
|
|
|
|
|
|
|
|
|
button = "<div style='width:6px; height:12px;'></div>";
|
|
|
|
|
|
|
|
|
|
for i in range(1,vol_val+1):
|
|
|
|
|
volume += ("<td class='volumeset'>" +
|
|
|
|
|
"<a href='javascript:dolink(\"action=setvolume&value="
|
|
|
|
|
+ str(i*10) + reqid + sesid + "\");'>" + button + "</a></td>")
|
|
|
|
|
|
|
|
|
|
for i in range(vol_val+1, 11):
|
|
|
|
|
volume += ("<td class='volumeunset'>" +
|
|
|
|
|
"<a href='javascript:dolink(\"action=setvolume&value="
|
|
|
|
|
+ str(i*10) + reqid + sesid + "\");'>" + button + "</a></td>")
|
|
|
|
|
|
|
|
|
|
volume += "</tr></table>"
|
|
|
|
|
return volume
|
|
|
|
|
|
|
|
|
|
def _createTimeStr(self, status):
|
|
|
|
|
"""Returns the string representation of the the remaining time of the current song."""
|
|
|
|
|
|
|
|
|
|
_gmtime = time.gmtime(status.timeLeft())
|
|
|
|
|
|
|
|
|
|
hours = status.timeLeft() / (60*60)
|
|
|
|
|
mins = (status.timeLeft() % (60*60)) / 60
|
|
|
|
|
secs = status.timeLeft() - 60 * mins - 60 * 60 * hours
|
|
|
|
|
|
|
|
|
|
hours_str = str(hours) + ":"
|
|
|
|
|
if hours < 10:
|
|
|
|
|
hours_str = "0" + hours_str
|
|
|
|
|
if hours == 0:
|
|
|
|
|
hours_str = ""
|
|
|
|
|
|
|
|
|
|
mins_str = str(mins) + ":"
|
|
|
|
|
if mins < 10:
|
|
|
|
|
mins_str = "0" + mins_str
|
|
|
|
|
|
|
|
|
|
secs_str = str(secs)
|
|
|
|
|
if secs < 10:
|
|
|
|
|
secs_str = "0" + secs_str
|
|
|
|
|
|
|
|
|
|
return hours_str + mins_str + secs_str
|
|
|
|
|
|
|
|
|
|
def _trackTimeStr(self, trackTime):
|
|
|
|
|
"""Returns the string representation of the track time of a song."""
|
|
|
|
|
|
|
|
|
|
_time = int(trackTime)
|
|
|
|
|
|
|
|
|
|
hours = _time / (60*60)
|
|
|
|
|
mins = (_time % (60*60)) / 60
|
|
|
|
|
secs = _time - 60 * mins - 60 * 60 * hours
|
|
|
|
|
|
|
|
|
|
hours_str = str(hours) + ":"
|
|
|
|
|
if hours < 10:
|
|
|
|
|
hours_str = "0" + hours_str
|
|
|
|
|
if hours == 0:
|
|
|
|
|
hours_str = ""
|
|
|
|
|
|
|
|
|
|
mins_str = str(mins) + ":"
|
|
|
|
|
if mins < 10:
|
|
|
|
|
mins_str = "0" + mins_str
|
|
|
|
|
|
|
|
|
|
secs_str = str(secs)
|
|
|
|
|
if secs < 10:
|
|
|
|
|
secs_str = "0" + secs_str
|
|
|
|
|
|
|
|
|
|
return hours_str + mins_str + secs_str
|
|
|
|
|
|
|
|
|
|
def _ratingStars(self, rating):
|
|
|
|
|
"""Returns a number of stars showing the rating of a song."""
|
|
|
|
|
|
|
|
|
|
_rating = int(rating)
|
|
|
|
|
|
|
|
|
|
_numBig = _rating / 2
|
|
|
|
|
_numSmall = _rating % 2
|
|
|
|
|
|
|
|
|
|
img_str=""
|
|
|
|
|
|
|
|
|
|
for i in range(1,_numBig+1):
|
|
|
|
|
img_str = img_str + "<img height='12' width='12' src='star.png'>"
|
|
|
|
|
|
|
|
|
|
if _numSmall> 0:
|
|
|
|
|
img_str = img_str + "<img height='12' width='12' src='smallstar.png'>"
|
|
|
|
|
|
|
|
|
|
return img_str
|
|
|
|
|
|
|
|
|
|
def _createActions(self, status):
|
|
|
|
|
"""Returns HTML for allactions that can be performed and for the time counter"""
|
|
|
|
|
|
|
|
|
|
if not status.controlsEnabled():
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
reqid = "&reqid=" + str(status.reqid)
|
|
|
|
|
sesid = "&sesid=" + str(status.sesid)
|
|
|
|
|
|
|
|
|
|
playpause = self._createButton("play", "play", reqid, sesid)
|
|
|
|
|
|
|
|
|
|
if status.isPlaying():
|
|
|
|
|
playpause = self._createButton("pause", "pause", reqid, sesid)
|
|
|
|
|
|
|
|
|
|
buttons = (playpause + " " +
|
|
|
|
|
self._createButton("stop", "stop", reqid, sesid) + " " +
|
|
|
|
|
self._createButton("start", "prev", reqid, sesid) + " " +
|
|
|
|
|
self._createButton("end", "next", reqid, sesid))
|
|
|
|
|
|
|
|
|
|
vol_val = int(float(status.getVolume()) / 10.0 + 0.5)
|
|
|
|
|
volume = self._createVolume(vol_val, reqid, sesid)
|
|
|
|
|
|
|
|
|
|
return actions % ( buttons, volume, self._createTimeStr(status) )
|
|
|
|
|
|
|
|
|
|
def _setFullPage(self, status):
|
|
|
|
|
"""Renders the fullpage into the varialbe self.fullPage"""
|
|
|
|
|
counter = ""
|
|
|
|
|
if status.isPlaying():
|
|
|
|
|
counter = "countdown(" + str(status.timeLeft()) + ");"
|
|
|
|
|
|
|
|
|
|
self.fullPage = code % ( counter,
|
|
|
|
|
self._createActions(status).encode(self.encoding,'replace'),
|
|
|
|
|
os.environ['LOGNAME'],
|
|
|
|
|
self._createTable(status).encode(self.encoding,'replace') )
|
|
|
|
|
|
|
|
|
|
def _getMtime(self):
|
|
|
|
|
"""gets the mtime from the current.xml file, to check if the current.xml
|
|
|
|
|
has been modified or not"""
|
|
|
|
|
return os.stat(PLAYLISTFILE)[-2]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _buildDoc(self):
|
|
|
|
|
"""Build the DOM-doc and calls the _extractTracks-Method"""
|
|
|
|
|
self.doc = minidom.parse(PLAYLISTFILE)
|
|
|
|
|
self._extractTracks()
|
|
|
|
|
|
|
|
|
|
def _extractTracks(self):
|
|
|
|
|
"""extracts all "item"-elements from the doc and creates an Track-object
|
|
|
|
|
to store the associated information"""
|
|
|
|
|
append = self.tracks.append
|
|
|
|
|
i = 1
|
|
|
|
|
for item in self.doc.getElementsByTagName("item"):
|
|
|
|
|
curTrack = Track()
|
|
|
|
|
for elem in FIELDS:
|
|
|
|
|
try:
|
|
|
|
|
value = item.getElementsByTagName(elem)[0].firstChild.nodeValue
|
|
|
|
|
except:
|
|
|
|
|
value = ' '
|
|
|
|
|
if elem == "Title":
|
|
|
|
|
value = value.strip()
|
|
|
|
|
if elem == "Length":
|
|
|
|
|
value = self._trackTimeStr(value);
|
|
|
|
|
if elem == "Rating":
|
|
|
|
|
value = self._ratingStars(value);
|
|
|
|
|
if elem == "TrackNo":
|
|
|
|
|
value = str(i)
|
|
|
|
|
i = i + 1
|
|
|
|
|
setattr(curTrack, elem, value)
|
|
|
|
|
append(curTrack)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _createTable(self, status):
|
|
|
|
|
"""Returns the HTML-Table"""
|
|
|
|
|
tbl = tblhead
|
|
|
|
|
rows = self._createRows(status)
|
|
|
|
|
thead = "<tr id='trackheader'>" + "".join(['<td>%s</td>'%(i) for i in FIELDS]) + "</tr>"
|
|
|
|
|
rowsstr = "".join(rows)
|
|
|
|
|
return tbl%("tracks", "<thead>" + thead + "</thead><tbody>" + rowsstr + "</tbody>")
|
|
|
|
|
|
|
|
|
|
def _createRows(self, status):
|
|
|
|
|
"""Returns the table rows"""
|
|
|
|
|
retval = []
|
|
|
|
|
i = 1
|
|
|
|
|
reqid = "&reqid=" + str(status.reqid)
|
|
|
|
|
sesid = "&sesid=" + str(status.sesid)
|
|
|
|
|
curindex = status.getActiveIndex()
|
|
|
|
|
id = None
|
|
|
|
|
for track in self.tracks:
|
|
|
|
|
style = 'tr_one'
|
|
|
|
|
if i %2 == 0:
|
|
|
|
|
style = 'tr_two'
|
|
|
|
|
if curindex == (i-1):
|
|
|
|
|
id = "nowplaying"
|
|
|
|
|
retval.append(track.toRow(style=style, id=id, trackno=(i-1), reqid=reqid, sesid=sesid))
|
|
|
|
|
i = i+1
|
|
|
|
|
return retval
|
|
|
|
|
|
|
|
|
|
def sync(self):
|
|
|
|
|
"""Saves the current Amarok-Playlist to the current.xml file. Calls
|
|
|
|
|
amarok via dcop"""
|
|
|
|
|
os.system("dcop amarok playlist saveCurrentPlaylist")
|