fix xmlparser exception on content with "&" symbol and auto-update mediaplayer screen
[enigma2-plugins.git] / cdinfo / src / plugin.py
1 from enigma import eServiceReference, eConsoleAppContainer
2 from Components.MediaPlayer import PlayList
3 import xml.dom.minidom
4 from Plugins.Plugin import PluginDescriptor
5 from Components.ActionMap import ActionMap
6 from Components.Label import Label
7 from Components.Button import Button
8 from Screens.Screen import Screen
9 from Components.config import config, ConfigSubsection, ConfigInteger, ConfigYesNo, ConfigText, getConfigListEntry
10 from Components.ConfigList import ConfigListScreen
11
12 config.plugins.CDInfo = ConfigSubsection()
13 config.plugins.CDInfo.useCDTEXT = ConfigYesNo(default = True)
14 config.plugins.CDInfo.useCDDB = ConfigYesNo(default = True)
15 config.plugins.CDInfo.displayString = ConfigText("$i - $t ($a)", fixed_size = False)
16 config.plugins.CDInfo.preferCDDB = ConfigYesNo(default = False)
17 config.plugins.CDInfo.CDDB_server = ConfigText("freedb.freedb.org", fixed_size = False)
18 config.plugins.CDInfo.CDDB_port = ConfigInteger(8880,limits = (1, 65536))
19 config.plugins.CDInfo.CDDB_timeout = ConfigInteger(20,limits = (-1, 60))
20 config.plugins.CDInfo.CDDB_cache = ConfigYesNo(default = True)
21
22 class CDInfo(ConfigListScreen,Screen):
23         skin = """
24                 <screen position="90,95" size="560,430" title="CDInfo" >
25                     <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" alphatest="on" />
26                     <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" alphatest="on" />
27                     <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" alphatest="on" />
28                     <widget name="key_red" position="0,0" zPosition="1" size="140,40" font="Regular;20" halign="center" valign="center" backgroundColor="#9f1313" transparent="1" />
29                     <widget name="key_green" position="140,0" zPosition="1" size="140,40" font="Regular;20" halign="center" valign="center" backgroundColor="#1f771f" transparent="1" />
30                     <widget name="key_blue" position="420,0" zPosition="1" size="140,40" font="Regular;20" halign="center" valign="center" backgroundColor="#18188b" transparent="1" />
31                     <widget name="info" position="20,50" size="520,40" font="Regular;20" transparent="1" />
32                     <widget name="config" position="20,120" size="520,200" scrollbarMode="showOnDemand" />
33                     <widget name="info2" position="20,340" size="520,80" font="Regular;20" transparent="1" />
34                 </screen>
35                 """
36         
37         def __init__(self, session, args = None):
38                 self.skin = CDInfo.skin
39                 Screen.__init__(self, session)
40
41                 self["info"] = Label("Gather audio cd album information and track listing from CDDB (online database) and / or CD-Text (from medium).")
42
43                 self["info2"] = Label("Playlist string variables: $i=track, $t=title, $a=artist\nCDDB query will not delay start of audio CD playback. The request will be sent asynchronously and playlist text will be updated when match was found.")
44
45                 self.list = []
46                 self.list.append(getConfigListEntry("Try to extract CDTEXT", config.plugins.CDInfo.useCDTEXT))
47                 self.list.append(getConfigListEntry("Try to query CDDB", config.plugins.CDInfo.useCDDB))
48                 self.list.append(getConfigListEntry("Playlist string", config.plugins.CDInfo.displayString))
49                 self.list.append(getConfigListEntry("CDDB overwrites CDTEXT info", config.plugins.CDInfo.preferCDDB))
50                 self.list.append(getConfigListEntry("CDDB server hostname", config.plugins.CDInfo.CDDB_server))
51                 self.list.append(getConfigListEntry("CDDB server port number", config.plugins.CDInfo.CDDB_port))
52                 self.list.append(getConfigListEntry("CDDB retrieval timeout (s)", config.plugins.CDInfo.CDDB_timeout))
53                 self.list.append(getConfigListEntry("store local CDDB cache", config.plugins.CDInfo.CDDB_cache))
54
55                 ConfigListScreen.__init__(self, self.list)
56                 self["key_red"] = Button(_("cancel"))
57                 self["key_green"] = Button(_("ok"))
58                 self["key_blue"] = Button(_("defaults"))
59
60                 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
61                 {
62                     "green": self.save,
63                     "red": self.cancel,
64                     "blue": self.defaults,
65                     "save": self.save,
66                     "cancel": self.cancel,
67                     "ok": self.save,
68                 }, -2)
69
70         def save(self):
71                 for x in self["config"].list:
72                         x[1].save()
73                 self.close(True)
74
75         def cancel(self):
76                 for x in self["config"].list:
77                         x[1].cancel()
78                 self.close(False)
79
80         def defaults(self):
81                 config.plugins.CDInfo.useCDTEXT.setValue(True)
82                 config.plugins.CDInfo.useCDDB.setValue(True)
83                 config.plugins.CDInfo.displayString.setValue("$i - $t ($a)")
84                 config.plugins.CDInfo.preferCDDB.setValue(False)
85                 config.plugins.CDInfo.CDDB_server.setValue("freedb.freedb.org")
86                 config.plugins.CDInfo.CDDB_port.setValue(8880)
87                 config.plugins.CDInfo.CDDB_timeout.setValue(20)
88                 config.plugins.CDInfo.CDDB_cache.setValue(True)
89                 for x in self["config"].list:
90                         x[1].save()
91                 self.close(True)
92
93 class Query:
94         def __init__(self, mediaplayer):
95                 self.playlist = mediaplayer.playlist
96                 self.mp = mediaplayer
97                 self.cddb_container = eConsoleAppContainer()
98                 self.cddb_output = ""
99                 self.cdtext_container = eConsoleAppContainer()
100                 self.cdtext_output = ""
101                 self.tracklisting = {}
102                 self.albuminfo = {}
103
104         def getText(self, nodelist):
105             rc = ""
106             for node in nodelist:
107                 if node.nodeType == node.TEXT_NODE:
108                     rc = rc + node.data
109             return rc.encode("utf-8")
110
111         def xml_parse_output(self,string):
112                 data = string.decode("utf-8").replace('&',"&amp;").encode("ascii",'xmlcharrefreplace')
113                 try:
114                         cdinfodom = xml.dom.minidom.parseString(data)
115                 except:
116                         print "[xml_parse_output] error, could not parse"
117                         return False
118                 xmldata = cdinfodom.childNodes[0]
119                 queries = xmldata.childNodes
120                 self.xml_parse_query(queries)
121                 print "[xml_parse_output] albuminfo: " + str(self.albuminfo)
122                 print "[xml_parse_output] tracklisting: " + str(self.tracklisting)
123                 return True
124
125         def xml_parse_query(self, queries_xml):
126             for queries in queries_xml:
127                 if queries.nodeType == xml.dom.minidom.Element.nodeType:
128                     if queries.tagName == 'query':
129                         print "[xml_parse_query] cdinfo source is %s, hit %s of %s" % (queries.getAttribute("source"),queries.getAttribute("match"),queries.getAttribute("num_matches"))
130                         for query in queries.childNodes:
131                             if query.nodeType == xml.dom.minidom.Element.nodeType:
132                                 if query.tagName == 'albuminfo':
133                                     self.xml_parse_albuminfo(query.childNodes)
134                                 elif query.tagName == 'tracklisting':
135                                     self.xml_parse_tracklisting(query.childNodes)
136
137         def xml_parse_albuminfo(self, albuminfo_xml):
138             for albuminfo in albuminfo_xml:
139                 if albuminfo.nodeType == xml.dom.minidom.Element.nodeType:
140                     if albuminfo.tagName == 'PERFORMER' or albuminfo.tagName == 'artist':
141                         artist = self.getText(albuminfo.childNodes)
142                         self.albuminfo["artist"] = artist
143                     elif albuminfo.tagName.upper() == 'TITLE':
144                         title = self.getText(albuminfo.childNodes)
145                         self.albuminfo["title"] = title
146                     elif albuminfo.tagName.upper() == 'YEAR':
147                         year = self.getText(albuminfo.childNodes)
148                         self.albuminfo["year"] = year
149                     elif albuminfo.tagName.upper() == 'GENRE':
150                         genre = self.getText(albuminfo.childNodes)
151                         self.albuminfo["genre"] = genre
152                     elif albuminfo.tagName == 'category' and not "GENRE" in self.albuminfo:
153                         category = self.getText(albuminfo.childNodes)
154                         self.albuminfo["genre"] = category
155
156         def xml_parse_tracklisting(self, tracklisting_xml):
157             for tracklist in tracklisting_xml:
158                 if tracklist.nodeType == xml.dom.minidom.Element.nodeType:
159                     if tracklist.tagName == 'track':
160                         index = int(tracklist.getAttribute("number"))
161                         trackinfo = {}
162                         for track in tracklist.childNodes:
163                             if track.nodeType == xml.dom.minidom.Element.nodeType:
164                                 if track.tagName == 'PERFORMER' or track.tagName == 'artist':
165                                     artist = self.getText(track.childNodes)
166                                     trackinfo["artist"] = artist
167                                 if track.tagName.upper() == 'TITLE':
168                                     title = self.getText(track.childNodes)
169                                     trackinfo["title"] = title
170                                 #elif track.tagName == 'length':
171                                     #tracktext += "Dauer=%ss " % self.getText(track.childNodes)
172                         self.tracklisting[index]=trackinfo
173
174         def updateAlbuminfo(self, replace = False):
175                 for tag in self.albuminfo:
176                         if tag not in self.mp.AudioCD_albuminfo or replace:
177                                 self.mp.AudioCD_albuminfo[tag] = self.albuminfo[tag]
178         
179         def updatePlaylist(self, replace = False):
180                 for idx in range(len(self.playlist)):
181                         ref = self.playlist.getServiceRefList()[idx]
182                         track = idx+1
183                         if idx < len(self.tracklisting):
184                                 if replace or not ref.getName():
185                                         trackinfo = self.tracklisting[track]
186                                         displayString = config.plugins.CDInfo.displayString.value.replace("$i", str(track))
187                                         if "title" in trackinfo:
188                                                 displayString = displayString.replace("$t", trackinfo["title"])
189                                         if "artist" in trackinfo:
190                                                 displayString = displayString.replace("$a", trackinfo["artist"])
191                                         ref.setName(displayString)
192                                         self.playlist.updateFile(idx, ref)
193                 self.playlist.updateList()
194
195         def scan(self):
196                 if config.plugins.CDInfo.useCDTEXT.value:
197                     self.cdtext_scan()
198                 if config.plugins.CDInfo.useCDDB.value:
199                     self.cddb_scan()
200
201         def cdtext_scan(self):
202                 cmd = "cdtextinfo -xalT"
203                 print "[cdtext_scan] " + cmd
204                 self.cdtext_container.appClosed.get().append(self.cdtext_finished)
205                 self.cdtext_container.dataAvail.get().append(self.cdtext_avail)
206                 self.cdtext_container.execute(cmd)
207
208         def cddb_scan(self):
209                 cmd = "cdtextinfo -xalD --cddb-port=%d --cddb-server=%s --cddb-timeout=%s" % (config.plugins.CDInfo.CDDB_port.value, config.plugins.CDInfo.CDDB_server.value, config.plugins.CDInfo.CDDB_timeout.value)
210                 if not config.plugins.CDInfo.CDDB_cache.value:
211                         cmd += " --no-cddb-cache"
212                 print "[cddb_scan] " + cmd
213                 self.cddb_container.appClosed.get().append(self.cddb_finished)
214                 self.cddb_container.dataAvail.get().append(self.cddb_avail)
215                 self.cddb_container.execute(cmd)
216
217         def cddb_avail(self,string):
218                 self.cddb_output += string
219
220         def cdtext_avail(self,string):
221                 self.cdtext_output += string
222
223         def cddb_finished(self,retval):
224                 self.cddb_container.appClosed.get().remove(self.cddb_finished)
225                 self.cddb_container.dataAvail.get().remove(self.cddb_avail)
226                 if not self.xml_parse_output(self.cddb_output):
227                         return
228                 if config.plugins.CDInfo.preferCDDB.value:
229                         self.updatePlaylist(replace = True)
230                         self.updateAlbuminfo(replace = True)
231                 else:
232                         self.updatePlaylist(replace = False)
233                         self.updateAlbuminfo(replace = False)
234                 self.mp.readTitleInformation()
235                 self.cddb_output = ""
236
237         def cdtext_finished(self,retval):
238                 self.cdtext_container.appClosed.get().remove(self.cdtext_finished)
239                 self.cdtext_container.dataAvail.get().remove(self.cdtext_avail)
240                 if not self.xml_parse_output(self.cdtext_output):
241                         return
242                 if not config.plugins.CDInfo.preferCDDB.value:
243                         self.updatePlaylist(replace = True)
244                         self.updateAlbuminfo(replace = True)
245                 else:
246                         self.updatePlaylist(replace = False)
247                         self.updateAlbuminfo(replace = False)
248                 self.mp.readTitleInformation()
249                 self.cdtext_output = ""
250
251 def main(session, **kwargs):
252         session.open(CDInfo)
253
254 def Plugins(**kwargs):
255         return [ PluginDescriptor(name="CDInfo", description="AudioCD info from CDDB & CD-Text", where = PluginDescriptor.WHERE_PLUGINMENU, fnc=main, icon="plugin.png") ]