[SHOUTcast] Screen layout enhanced
[enigma2-plugins.git] / shoutcast / src / plugin.py
1 #
2 #  SHOUTcast E2
3 #
4 #  $Id$
5 #
6 #  Coded by Dr.Best (c) 2010
7 #  Support: www.dreambox-tools.info
8 #
9 #  This plugin is licensed under the Creative Commons 
10 #  Attribution-NonCommercial-ShareAlike 3.0 Unported 
11 #  License. To view a copy of this license, visit
12 #  http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative
13 #  Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
14 #
15 #  Alternatively, this plugin may be distributed and executed on hardware which
16 #  is licensed by Dream Multimedia GmbH.
17
18 #  This plugin is NOT free software. It is open source, you are allowed to
19 #  modify it (if you keep the license), but it may not be commercially 
20 #  distributed other than under the conditions noted above.
21 #
22
23
24 from Plugins.Plugin import PluginDescriptor
25 from Screens.Screen import Screen
26 from Components.ActionMap import ActionMap
27 from Components.Label import Label
28 from enigma import eServiceReference
29 from enigma import eListboxPythonMultiContent, eListbox, gFont, \
30         RT_HALIGN_LEFT, RT_HALIGN_RIGHT, RT_VALIGN_CENTER
31 from Tools.LoadPixmap import LoadPixmap
32 import xml.etree.cElementTree
33 from Screens.InfoBarGenerics import InfoBarAudioSelection, InfoBarSeek
34 from Components.ServiceEventTracker import ServiceEventTracker
35 from enigma import iPlayableService, iServiceInformation
36
37 from twisted.internet import reactor, defer
38 from twisted.web import client
39 from twisted.web.client import HTTPClientFactory
40 from Components.Pixmap import Pixmap
41 from enigma import ePicLoad
42 from Components.ScrollLabel import ScrollLabel
43 import string
44 import os
45 from enigma import getDesktop
46 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigDirectory, ConfigYesNo, Config, ConfigInteger, ConfigSubList, ConfigText, getConfigListEntry, configfile
47 from Components.ConfigList import ConfigListScreen
48 from Screens.MessageBox import MessageBox
49 from Components.GUIComponent import GUIComponent
50 from Components.Sources.StaticText import StaticText
51 from urllib import quote
52 from twisted.web.client import downloadPage
53 from Screens.ChoiceBox import ChoiceBox
54 from Screens.VirtualKeyBoard import VirtualKeyBoard
55 from enigma import eTimer
56 from enigma import eConsoleAppContainer
57 from Components.Input import Input
58 from Screens.InputBox import InputBox
59 from Components.FileList import FileList
60 # for localized messages
61 from . import _
62
63
64 containerStreamripper = None
65 shoutcast_pluginversion = "1.0.0"
66
67 config.plugins.shoutcast = ConfigSubsection()
68 config.plugins.shoutcast.menu = ConfigSelection(default = "plugin", choices = [("plugin", _("Plugin menu")), ("extensions", _("Extensions menu"))])
69 config.plugins.shoutcast.name = ConfigText(default = _("SHOUTcast"), fixed_size = False, visible_width = 20)
70 config.plugins.shoutcast.description = ConfigText(default = _("Listen to SHOUTcast Internet Radio"), fixed_size = False, visible_width = 80)
71 config.plugins.shoutcast.streamingrate = ConfigSelection(default="0", choices = [("0",_("All")), ("64",_(">= 64 kbps")), ("128",_(">= 128 kbps")), ("192",_(">= 192 kbps")), ("256",_(">= 256 kbps"))])
72 config.plugins.shoutcast.reloadstationlist = ConfigSelection(default="0", choices = [("0",_("Off")), ("1",_("every minute")), ("3",_("every three minutes")), ("5",_("every five minutes"))])
73 config.plugins.shoutcast.dirname = ConfigDirectory(default = "/hdd/streamripper/")
74 config.plugins.shoutcast.riptosinglefile = ConfigYesNo(default = False)
75 config.plugins.shoutcast.createdirforeachstream = ConfigYesNo(default = True)
76 config.plugins.shoutcast.addsequenceoutputfile = ConfigYesNo(default = False)
77
78
79 class SHOUTcastGenre:
80         def __init__(self, name = ""):
81                 self.name = name
82
83 class SHOUTcastStation:
84         def __init__(self, name = "", mt = "", id = "", br = "", genre = "", ct = "", lc = ""):
85                 self.name = name
86                 self.mt = mt
87                 self.id = id
88                 self.br = br
89                 self.genre = genre
90                 self.ct = ct
91                 self.lc = lc
92
93 class Favorite:
94         def __init__(self, configItem = None):
95                 self.configItem = configItem
96
97 class myHTTPClientFactory(HTTPClientFactory):
98         def __init__(self, url, method='GET', postdata=None, headers=None,
99         agent="SHOUTcast", timeout=0, cookies=None,
100         followRedirect=1, lastModified=None, etag=None):
101                 HTTPClientFactory.__init__(self, url, method=method, postdata=postdata,
102                 headers=headers, agent=agent, timeout=timeout, cookies=cookies,followRedirect=followRedirect)
103
104 def sendUrlCommand(url, contextFactory=None, timeout=60, *args, **kwargs):
105         scheme, host, port, path = client._parse(url)
106         factory = myHTTPClientFactory(url, *args, **kwargs)
107         reactor.connectTCP(host, port, factory, timeout=timeout)
108         return factory.deferred
109
110
111 def main(session,**kwargs):
112         session.open(SHOUTcastWidget)
113
114 def Plugins(**kwargs):
115         list = []
116         if config.plugins.shoutcast.menu.value == "plugin":
117                 list.append (PluginDescriptor(
118                         name = config.plugins.shoutcast.name.value, 
119                         description = config.plugins.shoutcast.description.value + " "  + _("Ver.") + " " + shoutcast_pluginversion, 
120                         icon="plugin.png",
121                         where = PluginDescriptor.WHERE_PLUGINMENU,
122                         fnc=main)
123                 )
124         else:
125                 list.append (PluginDescriptor(
126                         name = config.plugins.shoutcast.name.value, 
127                         description = config.plugins.shoutcast.description.value + " "  + _("Ver.") + " " + shoutcast_pluginversion, 
128                         where = PluginDescriptor.WHERE_EXTENSIONSMENU, 
129                         fnc=main)
130                 )               
131         
132         return list
133
134 class SHOUTcastWidget(Screen, InfoBarSeek):
135
136         GENRELIST = 0
137         STATIONLIST = 1
138         FAVORITELIST = 2
139         SEARCHLIST = 3
140
141         STREAMRIPPER_BIN = '/usr/bin/streamripper'
142
143         FAVORITE_FILE_DEFAULT = '/usr/lib/enigma2/python/Plugins/Extensions/SHOUTcast/favorites'
144         FAVORITE_FILE = '/usr/lib/enigma2/python/Plugins/Extensions/SHOUTcast/favorites.user'
145
146         sz_w = getDesktop(0).size().width() - 90
147         sz_h = getDesktop(0).size().height() - 100
148         skin = """
149                 <screen name="SHOUTcastWidget" position="center,65" title="%s" size="%d,%d">
150                         <ePixmap position="5,0" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
151                         <ePixmap position="150,0" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
152                         <ePixmap position="295,0" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
153                         <ePixmap position="440,0" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
154                         <ePixmap pixmap="skin_default/buttons/key_menu.png" position="585,10" zPosition="0" size="35,25" alphatest="on" />
155                         <widget render="Label" source="key_red" position="5,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
156                         <widget render="Label" source="key_green" position="150,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
157                         <widget render="Label" source="key_yellow" position="295,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
158                         <widget render="Label" source="key_blue" position="440,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
159                         <widget name="headertext" position="5,47" zPosition="1" size="%d,23" font="Regular;20" transparent="1"  backgroundColor="#00000000"/>
160                         <widget name="statustext" position="5,240" zPosition="1" size="%d,90" font="Regular;20" halign="center" valign="center" transparent="0"  backgroundColor="#00000000"/>
161                         <widget name="list" position="5,80" zPosition="2" size="%d,%d" scrollbarMode="showOnDemand" transparent="0"  backgroundColor="#00000000"/>
162                         <widget name="titel" position="115,%d" zPosition="1" size="%d,40" font="Regular;18" transparent="1"  backgroundColor="#00000000"/>
163                         <widget name="station" position="115,%d" zPosition="1" size="%d,40" font="Regular;18" transparent="1"  backgroundColor="#00000000"/>
164                         <widget name="console" position="115,%d" zPosition="1" size="%d,40" font="Regular;18" transparent="1"  backgroundColor="#00000000"/>
165                         <widget name="cover" zPosition="2" position="5,%d" size="102,110" alphatest="blend" />
166                         <ePixmap position="%d,41" zPosition="4" size="120,35" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/SHOUTcast/shoutcast-logo1-fs8.png" transparent="1" alphatest="on" />
167                 </screen>""" %(
168                         config.plugins.shoutcast.name.value + " "  + _("Ver.") + " " + shoutcast_pluginversion, # title
169                         sz_w, sz_h, # size
170                         sz_w - 135, # size headertext
171                         sz_w - 100, # size statustext
172                         sz_w - 10, sz_h - 205, # size list
173                         sz_h - 105, # position titel
174                         sz_w - 125, # size titel
175                         sz_h - 70, # position station
176                         sz_w - 125, # size station
177                         sz_h - 25, # position console
178                         sz_w - 125, # size console
179                         sz_h - 105, # position cover
180                         sz_w - 125, # position logo
181                         )
182         
183         def __init__(self, session):
184                 self.session = session
185                 Screen.__init__(self, session)
186                 self.CurrentService = self.session.nav.getCurrentlyPlayingServiceReference()
187                 self.session.nav.stopService()
188                 self["cover"] = Cover()
189                 self["key_red"] = StaticText(_("Record"))
190                 self["key_green"] = StaticText(_("Genres"))
191                 self["key_yellow"] = StaticText(_("Stations"))
192                 self["key_blue"] = StaticText(_("Favorites"))
193                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
194                         {
195                                 iPlayableService.evUpdatedInfo: self.__evUpdatedInfo,
196                                 iPlayableService.evUser+10: self.__evAudioDecodeError,
197                                 iPlayableService.evUser+12: self.__evPluginError
198                         })
199                 InfoBarSeek.__init__(self, actionmap = "MediaPlayerSeekActions")
200                 self.mode = self.FAVORITELIST
201                 self["list"] = SHOUTcastList()
202                 self["list"].connectSelChanged(self.onSelectionChanged)
203                 self["statustext"] = Label(_("Getting SHOUTcast genre list..."))
204                 self["actions"] = ActionMap(["WizardActions", "DirectionActions", "ColorActions", "EPGSelectActions"],
205                 {
206                         "ok": self.ok_pressed,
207                         "back": self.close,
208                         "input_date_time": self.menu_pressed,
209                         "red": self.red_pressed,
210                         "green": self.green_pressed,
211                         "yellow": self.yellow_pressed,
212                         "blue": self.blue_pressed,
213                         
214                 }, -1)
215                 self.stationList = []
216                 self.stationListIndex = 0
217                 self.genreList = []
218                 self.genreListIndex = 0
219                 self.favoriteList = []
220                 self.favoriteListIndex = 0
221
222                 self.favoriteConfig = Config()
223                 if os.path.exists(self.FAVORITE_FILE):
224                         self.favoriteConfig.loadFromFile(self.FAVORITE_FILE)
225                 else:
226                         self.favoriteConfig.loadFromFile(self.FAVORITE_FILE_DEFAULT)
227                 self.favoriteConfig.entriescount =  ConfigInteger(0)
228                 self.favoriteConfig.Entries = ConfigSubList()
229                 self.initFavouriteConfig()
230                 self.stationListXML = ""
231                 self["titel"] = Label()
232                 self["station"] = Label()
233                 self["headertext"] = Label()
234                 self["console"] = Label()
235                 self.headerTextString = ""
236                 self.stationListHeader = ""
237                 self.tunein = ""
238                 self.searchSHOUTcastString = ""
239                 self.currentStreamingURL = ""
240                 self.currentStreamingStation = ""
241                 self.stationListURL = ""
242                 self.onClose.append(self.__onClose)
243                 self.onLayoutFinish.append(self.getFavoriteList)
244
245                 self.reloadStationListTimer = eTimer()
246                 self.reloadStationListTimer.timeout.get().append(self.reloadStationListTimerTimeout)
247                 self.reloadStationListTimerVar = int(config.plugins.shoutcast.reloadstationlist.value)
248
249                 self.visible = True
250
251                 global containerStreamripper
252                 if containerStreamripper is None:
253                         containerStreamripper = eConsoleAppContainer()
254
255                 containerStreamripper.dataAvail.append(self.streamripperDataAvail)
256                 containerStreamripper.appClosed.append(self.streamripperClosed)
257
258                 if containerStreamripper.running():
259                         self["key_red"].setText(_("Stop record"))
260                         # just to hear to recording music when starting the plugin...
261                         self.currentStreamingStation = _("Recording stream station")
262                         self.playServiceStream("http://localhost:9191")
263
264         def streamripperClosed(self, retval):
265                 if retval == 0:
266                         self["console"].setText("")
267                 self["key_red"].setText(_("Record"))
268
269         def streamripperDataAvail(self, data):
270                 sData = data.replace('\n','')
271                 self["console"].setText(sData)
272
273         def stopReloadStationListTimer(self):
274                 if self.reloadStationListTimer.isActive():
275                         self.reloadStationListTimer.stop()
276
277         def reloadStationListTimerTimeout(self):
278                 self.stopReloadStationListTimer()
279                 if self.mode == self.STATIONLIST:
280                         print "[SHOUTcast] reloadStationList: %s " % self.stationListURL
281                         sendUrlCommand(self.stationListURL, None,10).addCallback(self.callbackStationList).addErrback(self.callbackStationListError)
282
283         def InputBoxStartRecordingCallback(self, returnValue = None):
284                 if returnValue:
285                         recordingLength =  int(returnValue) * 60
286                         if not os.path.exists(config.plugins.shoutcast.dirname.value):
287                                 os.mkdir(config.plugins.shoutcast.dirname.value)
288                         args = []
289                         args.append(self.currentStreamingURL)
290                         args.append('-d')
291                         args.append(config.plugins.shoutcast.dirname.value)
292                         args.append('-r')
293                         args.append('9191')
294                         if recordingLength != 0:
295                                 args.append('-l')
296                                 args.append("%d" % int(recordingLength))
297                         if config.plugins.shoutcast.riptosinglefile.value:
298                                 args.append('-a')
299                                 args.append('-A')
300                         if not config.plugins.shoutcast.createdirforeachstream.value:
301                                 args.append('-s')
302                         if config.plugins.shoutcast.addsequenceoutputfile.value:
303                                 args.append('-q')
304                         cmd = [self.STREAMRIPPER_BIN, self.STREAMRIPPER_BIN] + args
305                         containerStreamripper.execute(*cmd)
306                         self["key_red"].setText(_("Stop record"))
307
308         def deleteRecordingConfirmed(self,val):
309                 if val:
310                         containerStreamripper.sendCtrlC()
311
312         def red_pressed(self):
313                 if containerStreamripper.running():
314                         self.session.openWithCallback(self.deleteRecordingConfirmed, MessageBox, _("Do you really want to stop the recording?"))
315                 else:
316                         if len(self.currentStreamingURL) != 0:
317                                 self.session.openWithCallback(self.InputBoxStartRecordingCallback, InputBox, windowTitle = _("Recording length"),  title=_("Enter in minutes (0 means unlimited)"), text="0", type=Input.NUMBER)
318                         else:
319                                 self.session.open(MessageBox, _("Only running streamings can be recorded!"), type = MessageBox.TYPE_INFO,timeout = 20 )
320
321         def green_pressed(self):
322                 if self.mode != self.GENRELIST:
323                         self.stopReloadStationListTimer()
324                         self.mode = self.GENRELIST
325                         if len(self.genreList):
326                                 self["headertext"].setText(_("SHOUTcast genre list"))
327                                 self["list"].setMode(self.mode)
328                                 self["list"].setList([ (x,) for x in self.genreList])
329                                 self["list"].moveToIndex(self.genreListIndex)
330                         else:
331                                 self.getGenreList()
332                 else:
333                         self.getGenreList()
334
335         def yellow_pressed(self):
336                 if self.mode != self.STATIONLIST:
337                         if len(self.stationList):
338                                 self.mode = self.STATIONLIST
339                                 self.headerTextString = _("SHOUTcast station list for %s") % self.stationListHeader
340                                 self["headertext"].setText(self.headerTextString)
341                                 self["list"].setMode(self.mode)
342                                 self["list"].setList([ (x,) for x in self.stationList])
343                                 self["list"].moveToIndex(self.stationListIndex)
344                                 if self.reloadStationListTimerVar != 0:
345                                         self.reloadStationListTimer.start(60000 * self.reloadStationListTimerVar)
346
347         def blue_pressed(self):
348                 if self.mode != self.FAVORITELIST:
349                         self.stopReloadStationListTimer()
350                         self.getFavoriteList(self.favoriteListIndex)
351
352         def getFavoriteList(self, favoriteListIndex = 0):
353                 self["statustext"].setText("")
354                 self.headerTextString = _("Favorite list")
355                 self["headertext"].setText(self.headerTextString)
356                 self.mode = self.FAVORITELIST
357                 self["list"].setMode(self.mode)
358                 favoriteList = []
359                 for item in self.favoriteConfig.Entries:
360                         favoriteList.append(Favorite(configItem=item))
361                 self["list"].setList([ (x,) for x in favoriteList])
362                 if len(favoriteList):
363                         self["list"].moveToIndex(favoriteListIndex)
364                 self["list"].show()
365
366         def getGenreList(self):
367                 self["headertext"].setText("")
368                 self["statustext"].setText(_("Getting SHOUTcast genre list..."))
369                 self["list"].hide()
370                 url = "http://yp.shoutcast.com/sbin/newxml.phtml"
371                 sendUrlCommand(url, None,10).addCallback(self.callbackGenreList).addErrback(self.callbackGenreListError)
372
373         def callbackGenreList(self, xmlstring):
374                 self["headertext"].setText(_("SHOUTcast genre list"))
375                 self.genreListIndex = 0
376                 self.mode = self.GENRELIST
377                 self["list"].setMode(self.mode)
378                 self.genreList = self.fillGenreList(xmlstring)
379                 self["statustext"].setText("")
380                 self["list"].setList([ (x,) for x in self.genreList])
381                 if len(self.genreList):
382                         self["list"].moveToIndex(self.genreListIndex)
383                 self["list"].show()
384
385         def callbackGenreListError(self, error = None):
386                 if error is not None:
387                         try:
388                                 self["list"].hide()
389                                 self["statustext"].setText(_("%s\nPress green-button to try again...") % str(error.getErrorMessage()))
390                         except: pass
391         
392                 
393         def fillGenreList(self,xmlstring):
394                 genreList = []
395                 try:
396                         root = xml.etree.cElementTree.fromstring(xmlstring)
397                 except: return []
398                 for childs in root.findall("genre"):
399                         genreList.append(SHOUTcastGenre(name = childs.get("name")))
400                 return genreList
401
402         
403         def onSelectionChanged(self):
404                 pass
405                 # till I find a better solution
406 #               if self.mode == self.STATIONLIST:
407 #                       self.stationListIndex = self["list"].getCurrentIndex()
408 #               elif self.mode == self.FAVORITELIST:
409 #                       self.favoriteListIndex = self["list"].getCurrentIndex()
410 #               elif self.mode == self.GENRELIST:
411 #                       self.genreListIndex = self["list"].getCurrentIndex()
412
413         def ok_pressed(self):
414                 if self.visible:
415                         sel = None
416                         try:
417                                 sel = self["list"].l.getCurrentSelection()[0]
418                         except:return
419                         if sel is None:
420                                 return
421                         else:
422                                 if self.mode == self.GENRELIST:
423                                         self.genreListIndex = self["list"].getCurrentIndex()
424                                         self.getStationList(sel.name)
425                                 elif self.mode == self.STATIONLIST:
426                                         self.stationListIndex = self["list"].getCurrentIndex()
427                                         self.stopPlaying()
428                                         url = "http://yp.shoutcast.com%s?id=%s" % (self.tunein, sel.id)
429                                         self["list"].hide()
430                                         self["statustext"].setText(_("Getting streaming data from\n%s") % sel.name)
431                                         self.currentStreamingStation = sel.name
432                                         sendUrlCommand(url, None,10).addCallback(self.callbackPLS).addErrback(self.callbackStationListError)
433                                 elif self.mode == self.FAVORITELIST:
434                                         self.favoriteListIndex = self["list"].getCurrentIndex()
435                                         if sel.configItem.type.value == "url":
436                                                 self.stopPlaying()
437                                                 self["headertext"].setText(self.headerTextString)
438                                                 self.currentStreamingStation = sel.configItem.name.value
439                                                 self.playServiceStream(sel.configItem.text.value)
440                                         elif sel.configItem.type.value == "pls":
441                                                 self.stopPlaying()
442                                                 url = sel.configItem.text.value
443                                                 self["list"].hide()
444                                                 self["statustext"].setText(_("Getting streaming data from\n%s") % sel.configItem.name.value)
445                                                 self.currentStreamingStation = sel.configItem.name.value
446                                                 sendUrlCommand(url, None,10).addCallback(self.callbackPLS).addErrback(self.callbackStationListError)
447                                         elif sel.configItem.type.value == "genre":
448                                                 self.getStationList(sel.configItem.name.value)
449                                 elif self.mode == self.SEARCHLIST and self.searchSHOUTcastString != "":
450                                         self.searchSHOUTcast(self.searchSHOUTcastString)
451                 else:
452                         self.showWindow()
453
454         def stopPlaying(self):
455                 self.currentStreamingURL = ""
456                 self.currentStreamingStation = ""
457                 self["headertext"].setText("")
458                 self["titel"].setText("")
459                 self["station"].setText("")
460                 self.summaries.setText("")
461                 self["cover"].hide()
462                 self.session.nav.stopService()
463
464         def callbackPLS(self, result):
465                 self["headertext"].setText(self.headerTextString)
466                 found = False
467                 parts = string.split(result,"\n")
468                 for lines in parts:
469                         if lines.find("File1=") != -1:
470                                 line = string.split(lines,"File1=")
471                                 found = True
472                                 self.playServiceStream(line[-1].rstrip().strip())
473                                 
474                 if found:
475                         self["statustext"].setText("")
476                         self["list"].show()
477                 else:
478                         self.currentStreamingStation = ""
479                         self["statustext"].setText(_("No streaming data found..."))
480
481         def getStationList(self,genre):
482                 self.stationListHeader = _("genre %s") % genre
483                 self.headerTextString = _("SHOUTcast station list for %s") % self.stationListHeader
484                 self["headertext"].setText("")
485                 self["statustext"].setText(_("Getting %s") %  self.headerTextString)
486                 self["list"].hide()
487                 self.stationListURL = "http://yp.shoutcast.com/sbin/newxml.phtml?genre=%s" % genre
488                 self.stationListIndex = 0
489                 sendUrlCommand(self.stationListURL, None,10).addCallback(self.callbackStationList).addErrback(self.callbackStationListError)
490
491         def callbackStationList(self, xmlstring):
492                 self.searchSHOUTcastString = ""
493                 self.stationListXML = xmlstring
494                 self["headertext"].setText(self.headerTextString)
495                 self.mode = self.STATIONLIST
496                 self["list"].setMode(self.mode)
497                 self.stationList = self.fillStationList(xmlstring)
498                 self["statustext"].setText("")
499                 self["list"].setList([ (x,) for x in self.stationList])
500                 if len(self.stationList):
501                         self["list"].moveToIndex(self.stationListIndex)
502                 self["list"].show()
503                 if self.reloadStationListTimerVar != 0:
504                         self.reloadStationListTimer.start(1000 * 60)
505
506         def fillStationList(self,xmlstring):
507                 stationList = []
508                 try:
509                         root = xml.etree.cElementTree.fromstring(xmlstring)
510                 except: return []
511                 config_bitrate = int(config.plugins.shoutcast.streamingrate.value)
512                 for childs in root.findall("tunein"):
513                         self.tunein = childs.get("base")
514                 for childs in root.findall("station"):
515                         try: bitrate = int(childs.get("br"))
516                         except: bitrate = 0
517                         if bitrate >= config_bitrate:
518                                 stationList.append(SHOUTcastStation(name = childs.get("name"), 
519                                                                         mt = childs.get("mt"), id = childs.get("id"), br = childs.get("br"), 
520                                                                         genre = childs.get("genre"), ct = childs.get("ct"), lc = childs.get("lc")))
521                 return stationList
522
523         def menu_pressed(self):
524                 if not self.visible:
525                         self.showWindow()
526                 options = [(_("Config"), self.config),(_("Search"), self.search),]
527                 if self.mode == self.FAVORITELIST and self.getSelectedItem() is not None:
528                         options.extend(((_("rename current selected favorite"), self.renameFavorite),))
529                         options.extend(((_("remove current selected favorite"), self.removeFavorite),))
530                 elif self.mode == self.GENRELIST and self.getSelectedItem() is not None:
531                         options.extend(((_("Add current selected genre to favorite"), self.addGenreToFavorite),))
532                 elif self.mode == self.STATIONLIST and self.getSelectedItem() is not None:
533                         options.extend(((_("Add current selected station to favorite"), self.addStationToFavorite),))
534                 if len(self.currentStreamingURL) != 0:
535                         options.extend(((_("Add current playing stream to favorite"), self.addCurrentStreamToFavorite),))
536                 options.extend(((_("Hide"), self.hideWindow),))
537                 self.session.openWithCallback(self.menuCallback, ChoiceBox,list = options)
538
539         def menuCallback(self, ret):
540                 ret and ret[1]()
541
542         def hideWindow(self):
543                 self.visible = False
544                 self.hide()
545
546         def showWindow(self):
547                 self.visible = True
548                 self.show()
549
550         def addGenreToFavorite(self):
551                 sel = self.getSelectedItem()
552                 if sel is not None:
553                         self.addFavorite(name = sel.name, text = sel.name, favoritetype = "genre")                      
554
555         def addStationToFavorite(self):
556                 sel = self.getSelectedItem()
557                 if sel is not None:
558                         self.addFavorite(name = sel.name, text = "http://yp.shoutcast.com%s?id=%s" % (self.tunein, sel.id), favoritetype = "pls", audio = sel.mt, bitrate = sel.br)                     
559
560         def addCurrentStreamToFavorite(self):
561                 self.addFavorite(name = self.currentStreamingStation, text = self.currentStreamingURL, favoritetype = "url")
562
563         def addFavorite(self, name = "", text = "", favoritetype = "", audio = "", bitrate = ""):
564                 self.favoriteConfig.entriescount.value = self.favoriteConfig.entriescount.value + 1
565                 self.favoriteConfig.entriescount.save()
566                 newFavorite = self.initFavouriteEntryConfig()
567                 newFavorite.name.value = name
568                 newFavorite.text.value = text
569                 newFavorite.type.value = favoritetype
570                 newFavorite.audio.value = audio
571                 newFavorite.bitrate.value = bitrate
572                 newFavorite.save()
573                 self.favoriteConfig.saveToFile(self.FAVORITE_FILE)
574
575         def renameFavorite(self):
576                 sel = self.getSelectedItem()
577                 if sel is not None:
578                         self.session.openWithCallback(self.renameFavoriteFinished, VirtualKeyBoard, title = _("Enter new name for favorite item"), text = sel.configItem.name.value)
579
580         def renameFavoriteFinished(self, text = None):
581                 if text:
582                         sel = self.getSelectedItem()
583                         sel.configItem.name.value = text
584                         sel.configItem.save()
585                         self.favoriteConfig.saveToFile(self.FAVORITE_FILE)
586                         self.favoriteListIndex = 0
587                         self.getFavoriteList()
588
589
590         def removeFavorite(self):
591                 sel = self.getSelectedItem()
592                 if sel is not None:
593                         self.favoriteConfig.entriescount.value = self.favoriteConfig.entriescount.value - 1
594                         self.favoriteConfig.entriescount.save()
595                         self.favoriteConfig.Entries.remove(sel.configItem)
596                         self.favoriteConfig.Entries.save()
597                         self.favoriteConfig.saveToFile(self.FAVORITE_FILE)
598                         self.favoriteListIndex = 0
599                         self.getFavoriteList()
600
601         def search(self):
602                 self.session.openWithCallback(self.searchSHOUTcast, VirtualKeyBoard, title = _("Enter text to search for"))
603
604         def searchSHOUTcast(self, searchstring = None):
605                 if searchstring:
606                         self.stopReloadStationListTimer()
607                         self.stationListHeader = _("search-criteria %s") % searchstring
608                         self.headerTextString = _("SHOUTcast station list for %s") % self.stationListHeader
609                         self["headertext"].setText("")
610                         self["statustext"].setText(_("Searching SHOUTcast for %s...") % searchstring)
611                         self["list"].hide()
612                         self.stationListURL = "http://yp.shoutcast.com/sbin/newxml.phtml?search=%s" % searchstring
613                         self.mode = self.SEARCHLIST
614                         self.searchSHOUTcastString = searchstring
615                         self.stationListIndex = 0
616                         sendUrlCommand(self.stationListURL, None,10).addCallback(self.callbackStationList).addErrback(self.callbackStationListError)
617
618         def config(self):
619                 self.stopReloadStationListTimer()
620                 self.session.openWithCallback(self.setupFinished, SHOUTcastSetup)
621
622         def setupFinished(self, result):
623                 if result:
624                         if self.mode == self.STATIONLIST:
625                                 self.reloadStationListTimerVar = int(config.plugins.shoutcast.reloadstationlist.value)
626                                 self.stationListIndex = 0
627                                 self.callbackStationList(self.stationListXML)
628
629         def callbackStationListError(self, error = None):
630                 if error is not None:
631                         try:
632                                 self["list"].hide()
633                                 self["statustext"].setText(_("%s\nPress OK to try again...") % str(error.getErrorMessage()))
634                         except: pass
635
636         def Error(self, error = None):
637                 if error is not None:
638                         try:
639                                 self["list"].hide()
640                                 self["statustext"].setText(str(error.getErrorMessage()))
641                         except: pass
642         
643         def __onClose(self):
644                 self.stopReloadStationListTimer()
645                 self.session.nav.playService(self.CurrentService)
646                 containerStreamripper.dataAvail.remove(self.streamripperDataAvail)
647                 containerStreamripper.appClosed.remove(self.streamripperClosed)
648
649         def GoogleImageCallback(self, result):
650                 foundPos = result.find("imgres?imgurl=")
651                 foundPos2 = result.find("&imgrefurl=")
652                 if foundPos != -1 and foundPos2 != -1:
653                         print "[SHOUTcast] downloading cover from %s " % result[foundPos+14:foundPos2]
654                         downloadPage(result[foundPos+14:foundPos2] ,"/tmp/.cover").addCallback(self.coverDownloadFinished).addErrback(self.coverDownloadFailed)
655
656         def coverDownloadFailed(self,result):
657                 print "[SHOUTcast] cover download failed: %s " % result
658                 self["cover"].hide()
659
660         def coverDownloadFinished(self,result):
661                 print "[SHOUTcast] cover download finished"
662                 self["cover"].updateIcon("/tmp/.cover")
663                 self["cover"].show()
664                 
665         def __evUpdatedInfo(self):
666                 sTitle = ""
667                 currPlay = self.session.nav.getCurrentService()
668                 if currPlay is not None:
669                         sTitle = currPlay.info().getInfoString(iServiceInformation.sTagTitle)
670                         if (len(sTitle) !=0):
671                                 url = "http://images.google.de/images?q=%s&btnG=Bilder-Suche" % quote(sTitle)
672                                 sendUrlCommand(url, None,10).addCallback(self.GoogleImageCallback).addErrback(self.Error)
673                 if len(sTitle) == 0:
674                         sTitle = "n/a"
675                 title = _("Title: %s") % sTitle
676                 self["titel"].setText(title)
677                 self.summaries.setText(title)
678
679
680         def __evAudioDecodeError(self):
681                 currPlay = self.session.nav.getCurrentService()
682                 sAudioType = currPlay.info().getInfoString(iServiceInformation.sUser+10)
683                 print "[SHOUTcast __evAudioDecodeError] audio-codec %s can't be decoded by hardware" % (sAudioType)
684                 self.session.open(MessageBox, _("This Dreambox can't decode %s streams!") % sAudioType, type = MessageBox.TYPE_INFO,timeout = 20 )
685
686         def __evPluginError(self):
687                 currPlay = self.session.nav.getCurrentService()
688                 message = currPlay.info().getInfoString(iServiceInformation.sUser+12)
689                 print "[SHOUTcast __evPluginError]" , message
690                 self.session.open(MessageBox, message, type = MessageBox.TYPE_INFO,timeout = 20 )
691
692         def doEofInternal(self, playing):
693                 self.stopPlaying()
694
695         def checkSkipShowHideLock(self):
696                 # nothing to do here
697                 pass
698         
699         def playServiceStream(self, url):
700                 self.session.nav.stopService()
701                 sref = eServiceReference(4097, 0, url)
702                 self.session.nav.playService(sref)
703                 self.currentStreamingURL = url
704                 self["titel"].setText(_("Title: n/a"))
705                 self["station"].setText(_("Station: %s") % self.currentStreamingStation)
706
707         def createSummary(self):
708                 return SHOUTcastLCDScreen
709
710         def initFavouriteEntryConfig(self):
711                 self.favoriteConfig.Entries.append(ConfigSubsection())
712                 i = len(self.favoriteConfig.Entries) -1
713                 self.favoriteConfig.Entries[i].name = ConfigText(default = "")
714                 self.favoriteConfig.Entries[i].text = ConfigText(default = "")
715                 self.favoriteConfig.Entries[i].type = ConfigText(default = "")
716                 self.favoriteConfig.Entries[i].audio = ConfigText(default = "")
717                 self.favoriteConfig.Entries[i].bitrate = ConfigText(default = "")
718                 return self.favoriteConfig.Entries[i]
719
720         def initFavouriteConfig(self):
721                 count = self.favoriteConfig.entriescount.value
722                 if count != 0:
723                         i = 0
724                         while i < count:
725                                 self.initFavouriteEntryConfig()
726                                 i += 1
727
728         def getSelectedItem(self):
729                 sel = None
730                 try:
731                         sel = self["list"].l.getCurrentSelection()[0]
732                 except:return None
733                 return sel
734
735 class Cover(Pixmap):
736         def __init__(self):
737                 Pixmap.__init__(self)
738                 self.picload = ePicLoad()
739                 self.picload.PictureData.get().append(self.paintIconPixmapCB)
740
741         def onShow(self):
742                 Pixmap.onShow(self)
743                 self.picload.setPara((self.instance.size().width(), self.instance.size().height(), 1, 1, False, 1, "#00000000"))
744
745         def paintIconPixmapCB(self, picInfo=None):
746                 ptr = self.picload.getData()
747                 if ptr != None:
748                         self.instance.setPixmap(ptr.__deref__())
749
750         def updateIcon(self, filename):
751                 self.picload.startDecode(filename)
752
753 class SHOUTcastList(GUIComponent, object):
754         def buildEntry(self, item):
755                 width = self.l.getItemSize().width()
756                 res = [ None ]
757                 if self.mode == 0: # GENRELIST
758                         res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width, 20, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, item.name))
759                 elif self.mode == 1: # STATIONLIST
760                         res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, item.name))
761                         res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 23, width, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, item.ct))
762                         res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 43, width / 2, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, _("Audio: %s") % item.mt))
763                         res.append((eListboxPythonMultiContent.TYPE_TEXT,  width / 2, 43, width / 2, 20, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, _("Bit rate: %s kbps") % item.br))
764                 elif self.mode == 2: # FAVORITELIST
765                         res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, item.configItem.name.value))
766                         res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 23, width, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, "%s (%s)" % (item.configItem.text.value, item.configItem.type.value)))
767                         if len(item.configItem.audio.value) != 0:
768                                 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 43, width / 2, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, _("Audio: %s") % item.configItem.audio.value))
769                         if len(item.configItem.bitrate.value) != 0:
770                                 res.append((eListboxPythonMultiContent.TYPE_TEXT,  width / 2, 43, width / 2, 20, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, _("Bit rate: %s kbps") % item.configItem.bitrate.value))
771                 return res
772
773         def __init__(self):
774                 GUIComponent.__init__(self)
775                 self.l = eListboxPythonMultiContent()
776                 self.l.setFont(0, gFont("Regular", 20))
777                 self.l.setFont(1, gFont("Regular", 18))
778                 self.l.setBuildFunc(self.buildEntry)
779                 self.l.setItemHeight(22)
780                 self.onSelectionChanged = [ ]
781                 self.mode = 0
782
783         def setMode(self, mode):
784                 self.mode = mode
785                 if mode == 0: # GENRELIST
786                         self.l.setItemHeight(22)
787                 elif mode == 1 or mode == 2: # STATIONLIST OR FAVORITELIST
788                         self.l.setItemHeight(63)
789
790         def connectSelChanged(self, fnc):
791                 if not fnc in self.onSelectionChanged:
792                         self.onSelectionChanged.append(fnc)
793
794         def disconnectSelChanged(self, fnc):
795                 if fnc in self.onSelectionChanged:
796                         self.onSelectionChanged.remove(fnc)
797
798         def selectionChanged(self):
799                 for x in self.onSelectionChanged:
800                         x()
801         
802         def getCurrent(self):
803                 cur = self.l.getCurrentSelection()
804                 return cur and cur[0]
805         
806         GUI_WIDGET = eListbox
807         
808         def postWidgetCreate(self, instance):
809                 instance.setContent(self.l)
810                 instance.selectionChanged.get().append(self.selectionChanged)
811
812         def preWidgetRemove(self, instance):
813                 instance.setContent(None)
814                 instance.selectionChanged.get().remove(self.selectionChanged)
815
816         def moveToIndex(self, index):
817                 self.instance.moveSelectionTo(index)
818
819         def getCurrentIndex(self):
820                 return self.instance.getCurrentIndex()
821
822         currentIndex = property(getCurrentIndex, moveToIndex)
823         currentSelection = property(getCurrent)
824
825         def setList(self, list):
826                 self.l.setList(list)
827
828 class SHOUTcastLCDScreen(Screen):
829         skin = """
830         <screen position="0,0" size="132,64" title="%s">
831                 <widget name="text1" position="4,0" size="132,14" font="Regular;12" halign="center" valign="center"/>
832                 <widget name="text2" position="4,14" size="132,49" font="Regular;10" halign="center" valign="center"/>
833         </screen>""" %(
834                 config.plugins.shoutcast.name.value # title
835                 )
836
837         def __init__(self, session, parent):
838                 Screen.__init__(self, session)
839                 self["text1"] = Label(config.plugins.shoutcast.name.value)
840                 self["text2"] = Label("")
841
842         def setText(self, text):
843                 self["text2"].setText(text)
844
845
846 class SHOUTcastSetup(Screen, ConfigListScreen):
847
848         skin = """
849                 <screen position="center,center" size="600,400" title="%s" >
850                         <ePixmap pixmap="skin_default/buttons/red.png" position="10,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
851                         <ePixmap pixmap="skin_default/buttons/green.png" position="155,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
852                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="300,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
853                         <ePixmap pixmap="skin_default/buttons/blue.png" position="445,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
854                         <widget render="Label" source="key_red" position="10,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
855                         <widget render="Label" source="key_green" position="150,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
856                         <widget name="config" position="10,50" size="580,400" scrollbarMode="showOnDemand" />
857                 </screen>""" %(
858                         config.plugins.shoutcast.name.value + " " + _("Setup") # title
859                         )
860
861         def __init__(self, session):
862                 Screen.__init__(self, session)
863
864                 self["key_red"] = StaticText(_("Cancel"))
865                 self["key_green"] = StaticText(_("OK"))
866
867                 self.list = [
868                         getConfigListEntry(_("Show in (needs GUI restart):"), config.plugins.shoutcast.menu),
869                         getConfigListEntry(_("Name (needs GUI restart):"), config.plugins.shoutcast.name),
870                         getConfigListEntry(_("Description:"), config.plugins.shoutcast.description),    
871                         getConfigListEntry(_("Streaming rate:"), config.plugins.shoutcast.streamingrate),
872                         getConfigListEntry(_("Reload station list:"), config.plugins.shoutcast.reloadstationlist),
873                         getConfigListEntry(_("Rip to single file, name is timestamped"), config.plugins.shoutcast.riptosinglefile),
874                         getConfigListEntry(_("Create a directory for each stream"), config.plugins.shoutcast.createdirforeachstream),
875                         getConfigListEntry(_("Add sequence number to output file"), config.plugins.shoutcast.addsequenceoutputfile),
876                                 ]
877                                 
878                 self.dirname = getConfigListEntry(_("Recording location:"), config.plugins.shoutcast.dirname)
879                 self.list.append(self.dirname)
880                 
881                 ConfigListScreen.__init__(self, self.list, session)
882                 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
883                 {
884                         "green": self.keySave,
885                         "cancel": self.keyClose,
886                         "ok": self.keySelect,
887                 }, -2)
888
889         def keySelect(self):
890                 cur = self["config"].getCurrent()
891                 if cur == self.dirname:
892                         self.session.openWithCallback(self.pathSelected,SHOUTcastStreamripperRecordingPath,config.plugins.shoutcast.dirname.value)
893
894         def pathSelected(self, res):
895                 if res is not None:
896                         config.plugins.shoutcast.dirname.value = res
897
898         def keySave(self):
899                 for x in self["config"].list:
900                         x[1].save()
901                 configfile.save()
902                 self.close(True)
903
904         def keyClose(self):
905                 for x in self["config"].list:
906                         x[1].cancel()
907                 self.close(False)
908
909
910 class SHOUTcastStreamripperRecordingPath(Screen):
911         skin = """<screen name="SHOUTcastStreamripperRecordingPath" position="center,center" size="560,320" title="Select record path for streamripper">
912                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
913                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
914                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
915                         <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
916                         <widget name="target" position="0,60" size="540,22" valign="center" font="Regular;22" />
917                         <widget name="filelist" position="0,100" zPosition="1" size="560,220" scrollbarMode="showOnDemand"/>
918                         <widget render="Label" source="key_red" position="0,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
919                         <widget render="Label" source="key_green" position="140,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
920                 </screen>"""
921                 
922         def __init__(self, session, initDir):
923                 Screen.__init__(self, session)
924                 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
925                 inhibitMounts = []
926                 self["filelist"] = FileList(initDir, showDirectories = True, showFiles = False, inhibitMounts = inhibitMounts, inhibitDirs = inhibitDirs)
927                 self["target"] = Label()
928                 self["actions"] = ActionMap(["WizardActions", "DirectionActions", "ColorActions", "EPGSelectActions"],
929                 {
930                         "back": self.cancel,
931                         "left": self.left,
932                         "right": self.right,
933                         "up": self.up,
934                         "down": self.down,
935                         "ok": self.ok,
936                         "green": self.green,
937                         "red": self.cancel
938                         
939                 }, -1)
940                 self["key_red"] = StaticText(_("Cancel"))
941                 self["key_green"] = StaticText(_("OK"))
942
943         def cancel(self):
944                 self.close(None)
945
946         def green(self):
947                 self.close(self["filelist"].getSelection()[0])
948
949         def up(self):
950                 self["filelist"].up()
951                 self.updateTarget()
952
953         def down(self):
954                 self["filelist"].down()
955                 self.updateTarget()
956
957         def left(self):
958                 self["filelist"].pageUp()
959                 self.updateTarget()
960
961         def right(self):
962                 self["filelist"].pageDown()
963                 self.updateTarget()
964
965         def ok(self):
966                 if self["filelist"].canDescent():
967                         self["filelist"].descent()
968                         self.updateTarget()
969
970         def updateTarget(self):
971                 currFolder = self["filelist"].getSelection()[0]
972                 if currFolder is not None:
973                         self["target"].setText(currFolder)
974                 else:
975                         self["target"].setText(_("Invalid Location"))
976