6 # Coded by Dr.Best (c) 2010
7 # Support: www.dreambox-tools.info
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.
15 # Alternatively, this plugin may be distributed and executed on hardware which
16 # is licensed by Dream Multimedia GmbH.
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.
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
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
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
62 containerStreamripper = None
64 config.plugins.shoutcast = ConfigSubsection()
65 config.plugins.shoutcast.streamingrate = ConfigSelection(default="0", choices = [("0",_("All")), ("64",_(">= 64 kbps")), ("128",_(">= 128 kbps")), ("192",_(">= 192 kbps")), ("256",_(">= 256 kbps"))])
66 config.plugins.shoutcast.reloadstationlist = ConfigSelection(default="0", choices = [("0",_("Off")), ("1",_("every minute")), ("3",_("every three minutes")), ("5",_("every five minutes"))])
67 config.plugins.shoutcast.dirname = ConfigDirectory(default = "/hdd/streamripper/")
68 config.plugins.shoutcast.riptosinglefile = ConfigYesNo(default = False)
69 config.plugins.shoutcast.createdirforeachstream = ConfigYesNo(default = True)
70 config.plugins.shoutcast.addsequenceoutputfile = ConfigYesNo(default = False)
74 def __init__(self, name = ""):
77 class SHOUTcastStation:
78 def __init__(self, name = "", mt = "", id = "", br = "", genre = "", ct = "", lc = ""):
88 def __init__(self, configItem = None):
89 self.configItem = configItem
91 class myHTTPClientFactory(HTTPClientFactory):
92 def __init__(self, url, method='GET', postdata=None, headers=None,
93 agent="SHOUTcast", timeout=0, cookies=None,
94 followRedirect=1, lastModified=None, etag=None):
95 HTTPClientFactory.__init__(self, url, method=method, postdata=postdata,
96 headers=headers, agent=agent, timeout=timeout, cookies=cookies,followRedirect=followRedirect)
98 def sendUrlCommand(url, contextFactory=None, timeout=60, *args, **kwargs):
99 scheme, host, port, path = client._parse(url)
100 factory = myHTTPClientFactory(url, *args, **kwargs)
101 reactor.connectTCP(host, port, factory, timeout=timeout)
102 return factory.deferred
105 def main(session,**kwargs):
106 session.open(SHOUTcastWidget)
108 def Plugins(**kwargs):
109 list = [PluginDescriptor(name="SHOUTcast", description=_("listen to shoutcast internet-radio"), where = [PluginDescriptor.WHERE_EXTENSIONSMENU], fnc=main)]
112 class SHOUTcastWidget(Screen, InfoBarSeek):
119 STREAMRIPPER_BIN = '/usr/bin/streamripper'
121 sz_w = getDesktop(0).size().width()
124 <screen name="SHOUTcastWidget" position="0,0" size="1280,720" flags="wfNoBorder" backgroundColor="#00000000" title="SHOUTcast">
125 <ePixmap position="50,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
126 <ePixmap position="200,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
127 <ePixmap position="350,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
128 <ePixmap position="500,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
129 <widget render="Label" source="key_red" position="50,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
130 <widget render="Label" source="key_green" position="200,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
131 <widget render="Label" source="key_yellow" position="350,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
132 <widget render="Label" source="key_blue" position="500,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
133 <widget name="headertext" position="50,77" zPosition="1" size="1180,23" font="Regular;20" transparent="1" backgroundColor="#00000000"/>
134 <widget name="statustext" position="20,270" zPosition="1" size="1240,90" font="Regular;20" halign="center" valign="center" transparent="0" backgroundColor="#00000000"/>
135 <widget name="list" position="50,110" zPosition="2" size="1180,445" scrollbarMode="showOnDemand" transparent="0" backgroundColor="#00000000"/>
136 <widget name="titel" position="160,580" zPosition="1" size="900,20" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
137 <widget name="station" position="160,600" zPosition="1" size="900,40" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
138 <widget name="console" position="160,650" zPosition="1" size="900,50" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
139 <widget name="cover" zPosition="2" position="50,580" size="102,110" alphatest="blend" />
140 <ePixmap position="1100,35" zPosition="4" size="120,35" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/SHOUTcast/shoutcast-logo1-fs8.png" transparent="1" alphatest="on" />
145 <screen name="SHOUTcastWidget" position="0,0" size="1024,576" flags="wfNoBorder" backgroundColor="#00000000" title="SHOUTcast">
146 <ePixmap position="50,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
147 <ePixmap position="200,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
148 <ePixmap position="350,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
149 <ePixmap position="500,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
150 <widget render="Label" source="key_red" position="50,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
151 <widget render="Label" source="key_green" position="200,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
152 <widget render="Label" source="key_yellow" position="350,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
153 <widget render="Label" source="key_blue" position="500,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
154 <widget name="headertext" position="50,77" zPosition="1" size="900,23" font="Regular;20" transparent="1" backgroundColor="#00000000"/>
155 <widget name="statustext" position="20,270" zPosition="1" size="1004,90" font="Regular;20" halign="center" valign="center" transparent="0" backgroundColor="#00000000"/>
156 <widget name="list" position="50,110" zPosition="2" size="940,313" scrollbarMode="showOnDemand" transparent="0" backgroundColor="#00000000"/>
157 <widget name="titel" position="160,450" zPosition="1" size="800,20" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
158 <widget name="station" position="160,470" zPosition="1" size="800,40" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
159 <widget name="console" position="160,520" zPosition="1" size="800,50" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
160 <widget name="cover" zPosition="2" position="50,450" size="102,110" alphatest="blend" />
161 <ePixmap position="870,35" zPosition="4" size="120,35" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/SHOUTcast/shoutcast-logo1-fs8.png" transparent="1" alphatest="on" />
166 <screen name="SHOUTcastWidget" position="0,0" size="720,576" flags="wfNoBorder" backgroundColor="#00000000" title="SHOUTcast">
167 <ePixmap position="20,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
168 <ePixmap position="160,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
169 <ePixmap position="300,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
170 <ePixmap position="440,30" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
171 <widget render="Label" source="key_red" position="20,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
172 <widget render="Label" source="key_green" position="160,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
173 <widget render="Label" source="key_yellow" position="300,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
174 <widget render="Label" source="key_blue" position="440,30" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
175 <widget name="headertext" position="20,77" zPosition="1" size="680,23" font="Regular;20" transparent="1" backgroundColor="#00000000"/>
176 <widget name="statustext" position="20,270" zPosition="1" size="680,90" font="Regular;20" halign="center" valign="center" transparent="0" backgroundColor="#00000000"/>
177 <widget name="list" position="20,110" zPosition="2" size="680,313" scrollbarMode="showOnDemand" transparent="0" backgroundColor="#00000000"/>
178 <widget name="titel" position="130,450" zPosition="1" size="550,20" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
179 <widget name="station" position="130,470" zPosition="1" size="550,40" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
180 <widget name="console" position="130,520" zPosition="1" size="550,50" font="Regular;18" transparent="1" backgroundColor="#00000000"/>
181 <widget name="cover" zPosition="2" position="20,450" size="102,110" alphatest="blend" />
182 <ePixmap position="590,35" zPosition="4" size="120,35" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/SHOUTcast/shoutcast-logo1-fs8.png" transparent="1" alphatest="on" />
186 def __init__(self, session):
187 self.session = session
188 Screen.__init__(self, session)
189 self.CurrentService = self.session.nav.getCurrentlyPlayingServiceReference()
190 self.session.nav.stopService()
191 self["cover"] = Cover()
192 self["key_red"] = StaticText(_("Record"))
193 self["key_green"] = StaticText(_("Genres"))
194 self["key_yellow"] = StaticText(_("Stations"))
195 self["key_blue"] = StaticText(_("Favorites"))
196 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
198 iPlayableService.evUpdatedInfo: self.__evUpdatedInfo,
199 iPlayableService.evUser+10: self.__evAudioDecodeError,
200 iPlayableService.evUser+12: self.__evPluginError
202 InfoBarSeek.__init__(self, actionmap = "MediaPlayerSeekActions")
203 self.mode = self.FAVORITELIST
204 self["list"] = SHOUTcastList()
205 self["list"].connectSelChanged(self.onSelectionChanged)
206 self["statustext"] = Label(_("Getting SHOUTcast genre list..."))
207 self["actions"] = ActionMap(["WizardActions", "DirectionActions", "ColorActions", "EPGSelectActions"],
209 "ok": self.ok_pressed,
211 "input_date_time": self.menu_pressed,
212 "red": self.red_pressed,
213 "green": self.green_pressed,
214 "yellow": self.yellow_pressed,
215 "blue": self.blue_pressed,
218 self.stationList = []
219 self.stationListIndex = 0
221 self.genreListIndex = 0
222 self.favoriteList = []
223 self.favoriteListIndex = 0
225 self.favoriteConfigFile = "/usr/lib/enigma2/python/Plugins/Extensions/SHOUTcast/favorites"
226 self.favoriteConfig = Config()
227 if os.path.exists(self.favoriteConfigFile):
228 self.favoriteConfig.loadFromFile(self.favoriteConfigFile)
229 self.favoriteConfig.entriescount = ConfigInteger(0)
230 self.favoriteConfig.Entries = ConfigSubList()
231 self.initFavouriteConfig()
233 self.stationListXML = ""
234 self["titel"] = Label()
235 self["station"] = Label()
236 self["headertext"] = Label()
237 self["console"] = Label()
238 self.headerTextString = ""
239 self.stationListHeader = ""
241 self.searchSHOUTcastString = ""
242 self.currentStreamingURL = ""
243 self.currentStreamingStation = ""
244 self.stationListURL = ""
245 self.onClose.append(self.__onClose)
246 self.onLayoutFinish.append(self.getFavoriteList)
248 self.reloadStationListTimer = eTimer()
249 self.reloadStationListTimer.timeout.get().append(self.reloadStationListTimerTimeout)
250 self.reloadStationListTimerVar = int(config.plugins.shoutcast.reloadstationlist.value)
254 global containerStreamripper
255 if containerStreamripper is None:
256 containerStreamripper = eConsoleAppContainer()
258 containerStreamripper.dataAvail.append(self.streamripperDataAvail)
259 containerStreamripper.appClosed.append(self.streamripperClosed)
261 if containerStreamripper.running():
262 # just to hear to recording music when starting the plugin...
263 self.currentStreamingStation = _("Recording stream station")
264 self.playServiceStream("http://localhost:9191")
266 def streamripperClosed(self, retval):
268 self["console"].setText("")
270 def streamripperDataAvail(self, data):
271 sData = data.replace('\n','')
272 self["console"].setText(sData)
274 def stopReloadStationListTimer(self):
275 if self.reloadStationListTimer.isActive():
276 self.reloadStationListTimer.stop()
278 def reloadStationListTimerTimeout(self):
279 self.stopReloadStationListTimer()
280 if self.mode == self.STATIONLIST:
281 print "[SHOUTcast] reloadStationList: %s " % self.stationListURL
282 sendUrlCommand(self.stationListURL, None,10).addCallback(self.callbackStationList).addErrback(self.callbackStationListError)
284 def InputBoxStartRecordingCallback(self, returnValue = None):
287 recordingLength = int(returnValue) * 60
288 if not os.path.exists(config.plugins.shoutcast.dirname.value):
289 os.mkdir(config.plugins.shoutcast.dirname.value)
291 args.append(self.currentStreamingURL)
293 args.append(config.plugins.shoutcast.dirname.value)
296 if recordingLength != 0:
298 args.append("%d" % int(recordingLength))
299 if config.plugins.shoutcast.riptosinglefile.value:
302 if not config.plugins.shoutcast.createdirforeachstream.value:
304 if config.plugins.shoutcast.addsequenceoutputfile.value:
306 cmd = [self.STREAMRIPPER_BIN, self.STREAMRIPPER_BIN] + args
307 containerStreamripper.execute(*cmd)
309 def red_pressed(self):
310 if containerStreamripper.running():
311 containerStreamripper.sendCtrlC()
313 if len(self.currentStreamingURL) != 0:
314 self.session.openWithCallback(self.InputBoxStartRecordingCallback, InputBox, windowTitle = _("Recording length"), title=_("Enter in minutes (0 means unlimited)"), text="0", type=Input.NUMBER)
316 self.session.open(MessageBox, _("Only running streamings can be recorded!"), type = MessageBox.TYPE_INFO,timeout = 20 )
318 def green_pressed(self):
319 if self.mode != self.GENRELIST:
320 self.stopReloadStationListTimer()
321 self.mode = self.GENRELIST
322 if len(self.genreList):
323 self["headertext"].setText("SHOUTcast genre-list")
324 self["list"].setMode(self.mode)
325 self["list"].setList([ (x,) for x in self.genreList])
326 self["list"].moveToIndex(self.genreListIndex)
332 def yellow_pressed(self):
333 if self.mode != self.STATIONLIST:
334 if len(self.stationList):
335 self.mode = self.STATIONLIST
336 self.headerTextString = _("SHOUTcast station list for %s") % self.stationListHeader
337 self["headertext"].setText(self.headerTextString)
338 self["list"].setMode(self.mode)
339 self["list"].setList([ (x,) for x in self.stationList])
340 self["list"].moveToIndex(self.stationListIndex)
341 if self.reloadStationListTimerVar != 0:
342 self.reloadStationListTimer.start(60000 * self.reloadStationListTimerVar)
344 def blue_pressed(self):
345 if self.mode != self.FAVORITELIST:
346 self.stopReloadStationListTimer()
347 self.getFavoriteList(self.favoriteListIndex)
349 def getFavoriteList(self, favoriteListIndex = 0):
350 self["statustext"].setText("")
351 self.headerTextString = _("Favorite list")
352 self["headertext"].setText(self.headerTextString)
353 self.mode = self.FAVORITELIST
354 self["list"].setMode(self.mode)
356 for item in self.favoriteConfig.Entries:
357 favoriteList.append(Favorite(configItem=item))
358 self["list"].setList([ (x,) for x in favoriteList])
359 if len(favoriteList):
360 self["list"].moveToIndex(favoriteListIndex)
363 def getGenreList(self):
364 self["headertext"].setText("")
365 self["statustext"].setText(_("Getting SHOUTcast genre list..."))
367 url = "http://yp.shoutcast.com/sbin/newxml.phtml"
368 sendUrlCommand(url, None,10).addCallback(self.callbackGenreList).addErrback(self.callbackGenreListError)
370 def callbackGenreList(self, xmlstring):
371 self["headertext"].setText("SHOUTcast genre list")
372 self.genreListIndex = 0
373 self.mode = self.GENRELIST
374 self["list"].setMode(self.mode)
375 self.genreList = self.fillGenreList(xmlstring)
376 self["statustext"].setText("")
377 self["list"].setList([ (x,) for x in self.genreList])
378 if len(self.genreList):
379 self["list"].moveToIndex(self.genreListIndex)
382 def callbackGenreListError(self, error = None):
383 if error is not None:
386 self["statustext"].setText(_("%s\nPress green-button to try again...") % str(error.getErrorMessage()))
390 def fillGenreList(self,xmlstring):
393 root = xml.etree.cElementTree.fromstring(xmlstring)
395 for childs in root.findall("genre"):
396 genreList.append(SHOUTcastGenre(name = childs.get("name")))
400 def onSelectionChanged(self):
402 # till I find a better solution
403 # if self.mode == self.STATIONLIST:
404 # self.stationListIndex = self["list"].getCurrentIndex()
405 # elif self.mode == self.FAVORITELIST:
406 # self.favoriteListIndex = self["list"].getCurrentIndex()
407 # elif self.mode == self.GENRELIST:
408 # self.genreListIndex = self["list"].getCurrentIndex()
410 def ok_pressed(self):
414 sel = self["list"].l.getCurrentSelection()[0]
419 if self.mode == self.GENRELIST:
420 self.genreListIndex = self["list"].getCurrentIndex()
421 self.getStationList(sel.name)
422 elif self.mode == self.STATIONLIST:
423 self.stationListIndex = self["list"].getCurrentIndex()
425 url = "http://yp.shoutcast.com%s?id=%s" % (self.tunein, sel.id)
427 self["statustext"].setText("Getting streaming data from\n%s" % sel.name)
428 self.currentStreamingStation = sel.name
429 sendUrlCommand(url, None,10).addCallback(self.callbackPLS).addErrback(self.callbackStationListError)
430 elif self.mode == self.FAVORITELIST:
431 self.favoriteListIndex = self["list"].getCurrentIndex()
432 if sel.configItem.type.value == "url":
434 self["headertext"].setText(self.headerTextString)
435 self.currentStreamingStation = sel.configItem.name.value
436 self.playServiceStream(sel.configItem.text.value)
437 elif sel.configItem.type.value == "pls":
439 url = sel.configItem.text.value
441 self["statustext"].setText(_("Getting streaming data from\n%s") % sel.configItem.name.value)
442 self.currentStreamingStation = sel.configItem.name.value
443 sendUrlCommand(url, None,10).addCallback(self.callbackPLS).addErrback(self.callbackStationListError)
444 elif sel.configItem.type.value == "genre":
445 self.getStationList(sel.configItem.name.value)
446 elif self.mode == self.SEARCHLIST and self.searchSHOUTcastString != "":
447 self.searchSHOUTcast(self.searchSHOUTcastString)
451 def stopPlaying(self):
452 self.currentStreamingURL = ""
453 self.currentStreamingStation = ""
454 self["headertext"].setText("")
455 self["titel"].setText("")
456 self["station"].setText("")
457 self.summaries.setText("")
459 self.session.nav.stopService()
461 def callbackPLS(self, result):
462 self["headertext"].setText(self.headerTextString)
464 parts = string.split(result,"\n")
466 if lines.find("File1=") != -1:
467 line = string.split(lines,"File1=")
469 self.playServiceStream(line[-1])
472 self["statustext"].setText("")
475 self.currentStreamingStation = ""
476 self["statustext"].setText(_("No streaming data found..."))
478 def getStationList(self,genre):
479 self.stationListHeader = "genre %s" % genre
480 self.headerTextString = _("SHOUTcast station list for %s") % self.stationListHeader
481 self["headertext"].setText("")
482 self["statustext"].setText(_("Getting %s") % self.headerTextString)
484 self.stationListURL = "http://yp.shoutcast.com/sbin/newxml.phtml?genre=%s" % genre
485 self.stationListIndex = 0
486 sendUrlCommand(self.stationListURL, None,10).addCallback(self.callbackStationList).addErrback(self.callbackStationListError)
488 def callbackStationList(self, xmlstring):
489 self.searchSHOUTcastString = ""
490 self.stationListXML = xmlstring
491 self["headertext"].setText(self.headerTextString)
492 self.mode = self.STATIONLIST
493 self["list"].setMode(self.mode)
494 self.stationList = self.fillStationList(xmlstring)
495 self["statustext"].setText("")
496 self["list"].setList([ (x,) for x in self.stationList])
497 if len(self.stationList):
498 self["list"].moveToIndex(self.stationListIndex)
500 if self.reloadStationListTimerVar != 0:
501 self.reloadStationListTimer.start(1000 * 60)
503 def fillStationList(self,xmlstring):
506 root = xml.etree.cElementTree.fromstring(xmlstring)
508 config_bitrate = int(config.plugins.shoutcast.streamingrate.value)
509 for childs in root.findall("tunein"):
510 self.tunein = childs.get("base")
511 for childs in root.findall("station"):
512 try: bitrate = int(childs.get("br"))
514 if bitrate >= config_bitrate:
515 stationList.append(SHOUTcastStation(name = childs.get("name"),
516 mt = childs.get("mt"), id = childs.get("id"), br = childs.get("br"),
517 genre = childs.get("genre"), ct = childs.get("ct"), lc = childs.get("lc")))
520 def menu_pressed(self):
523 options = [(_("Config"), self.config),(_("Search"), self.search),]
524 if self.mode == self.FAVORITELIST and self.getSelectedItem() is not None:
525 options.extend(((_("rename current selected favorite"), self.renameFavorite),))
526 options.extend(((_("remove current selected favorite"), self.removeFavorite),))
527 elif self.mode == self.GENRELIST and self.getSelectedItem() is not None:
528 options.extend(((_("Add current selected genre to favorite"), self.addGenreToFavorite),))
529 elif self.mode == self.STATIONLIST and self.getSelectedItem() is not None:
530 options.extend(((_("Add current selected station to favorite"), self.addStationToFavorite),))
531 if len(self.currentStreamingURL) != 0:
532 options.extend(((_("Add current playing stream to favorite"), self.addCurrentStreamToFavorite),))
533 options.extend(((_("Hide"), self.hideWindow),))
534 self.session.openWithCallback(self.menuCallback, ChoiceBox,list = options)
536 def menuCallback(self, ret):
539 def hideWindow(self):
543 def showWindow(self):
547 def addGenreToFavorite(self):
548 sel = self.getSelectedItem()
550 self.addFavorite(name = sel.name, text = sel.name, favoritetype = "genre")
552 def addStationToFavorite(self):
553 sel = self.getSelectedItem()
555 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)
557 def addCurrentStreamToFavorite(self):
558 self.addFavorite(name = self.currentStreamingStation, text = self.currentStreamingURL, favoritetype = "url")
560 def addFavorite(self, name = "", text = "", favoritetype = "", audio = "", bitrate = ""):
561 self.favoriteConfig.entriescount.value = self.favoriteConfig.entriescount.value + 1
562 self.favoriteConfig.entriescount.save()
563 newFavorite = self.initFavouriteEntryConfig()
564 newFavorite.name.value = name
565 newFavorite.text.value = text
566 newFavorite.type.value = favoritetype
567 newFavorite.audio.value = audio
568 newFavorite.bitrate.value = bitrate
570 self.favoriteConfig.saveToFile(self.favoriteConfigFile)
572 def renameFavorite(self):
573 sel = self.getSelectedItem()
575 self.session.openWithCallback(self.renameFavoriteFinished, VirtualKeyBoard, title = _("Enter new name for favorite item"), text = sel.configItem.name.value)
577 def renameFavoriteFinished(self, text = None):
579 sel = self.getSelectedItem()
580 sel.configItem.name.value = text
581 sel.configItem.save()
582 self.favoriteConfig.saveToFile(self.favoriteConfigFile)
583 self.favoriteListIndex = 0
584 self.getFavoriteList()
587 def removeFavorite(self):
588 sel = self.getSelectedItem()
590 self.favoriteConfig.entriescount.value = self.favoriteConfig.entriescount.value - 1
591 self.favoriteConfig.entriescount.save()
592 self.favoriteConfig.Entries.remove(sel.configItem)
593 self.favoriteConfig.Entries.save()
594 self.favoriteConfig.saveToFile(self.favoriteConfigFile)
595 self.favoriteListIndex = 0
596 self.getFavoriteList()
599 self.session.openWithCallback(self.searchSHOUTcast, VirtualKeyBoard, title = _("Enter text to search for"))
601 def searchSHOUTcast(self, searchstring = None):
603 self.stopReloadStationListTimer()
604 self.stationListHeader = _("search-cirteria %s") % searchstring
605 self.headerTextString = _("(SHOUTcast station list for %s") % self.stationListHeader
606 self["headertext"].setText("")
607 self["statustext"].setText(_("Searching SHOUTcast for %s...") % searchstring)
609 self.stationListURL = "http://yp.shoutcast.com/sbin/newxml.phtml?search=%s" % searchstring
610 self.mode = self.SEARCHLIST
611 self.searchSHOUTcastString = searchstring
612 self.stationListIndex = 0
613 sendUrlCommand(self.stationListURL, None,10).addCallback(self.callbackStationList).addErrback(self.callbackStationListError)
616 self.stopReloadStationListTimer()
617 self.session.openWithCallback(self.setupFinished, SHOUTcastSetup)
619 def setupFinished(self, result):
621 if self.mode == self.STATIONLIST:
622 self.reloadStationListTimerVar = int(config.plugins.shoutcast.reloadstationlist.value)
623 self.stationListIndex = 0
624 self.callbackStationList(self.stationListXML)
626 def callbackStationListError(self, error = None):
627 if error is not None:
630 self["statustext"].setText(_("%s\nPress OK to try again...") % str(error.getErrorMessage()))
633 def Error(self, error = None):
634 if error is not None:
637 self["statustext"].setText(str(error.getErrorMessage()))
641 self.stopReloadStationListTimer()
642 self.session.nav.playService(self.CurrentService)
643 containerStreamripper.dataAvail.remove(self.streamripperDataAvail)
644 containerStreamripper.appClosed.remove(self.streamripperClosed)
646 def GoogleImageCallback(self, result):
647 foundPos = result.find("imgres?imgurl=")
648 foundPos2 = result.find("&imgrefurl=")
649 if foundPos != -1 and foundPos2 != -1:
650 print "[SHOUTcast] downloading cover from %s " % result[foundPos+14:foundPos2]
651 downloadPage(result[foundPos+14:foundPos2] ,"/tmp/.cover").addCallback(self.coverDownloadFinished).addErrback(self.coverDownloadFailed)
653 def coverDownloadFailed(self,result):
654 print "[SHOUTcast] cover download failed: %s " % result
657 def coverDownloadFinished(self,result):
658 print "[SHOUTcast] cover download finished"
659 self["cover"].updateIcon("/tmp/.cover")
662 def __evUpdatedInfo(self):
664 currPlay = self.session.nav.getCurrentService()
665 if currPlay is not None:
666 sTitle = currPlay.info().getInfoString(iServiceInformation.sTagTitle)
667 if (len(sTitle) !=0):
668 url = "http://images.google.de/images?q=%s&btnG=Bilder-Suche" % quote(sTitle)
669 sendUrlCommand(url, None,10).addCallback(self.GoogleImageCallback).addErrback(self.Error)
672 title = _("Title: %s") % sTitle
673 self["titel"].setText(title)
674 self.summaries.setText(title)
677 def __evAudioDecodeError(self):
678 currPlay = self.session.nav.getCurrentService()
679 sAudioType = currPlay.info().getInfoString(iServiceInformation.sUser+10)
680 print "[SHOUTcast __evAudioDecodeError] audio-codec %s can't be decoded by hardware" % (sAudioType)
681 self.session.open(MessageBox, _("This Dreambox can't decode %s streams!") % sAudioType, type = MessageBox.TYPE_INFO,timeout = 20 )
683 def __evPluginError(self):
684 currPlay = self.session.nav.getCurrentService()
685 message = currPlay.info().getInfoString(iServiceInformation.sUser+12)
686 print "[SHOUTcast __evPluginError]" , message
687 self.session.open(MessageBox, message, type = MessageBox.TYPE_INFO,timeout = 20 )
689 def doEofInternal(self, playing):
692 def checkSkipShowHideLock(self):
696 def playServiceStream(self, url):
697 self.session.nav.stopService()
698 sref = eServiceReference(4097, 0, url)
699 self.session.nav.playService(sref)
700 self.currentStreamingURL = url
701 self["titel"].setText(_("Title: n/a"))
702 self["station"].setText(_("Station: %s") % self.currentStreamingStation)
704 def createSummary(self):
705 return SHOUTcastLCDScreen
707 def initFavouriteEntryConfig(self):
708 self.favoriteConfig.Entries.append(ConfigSubsection())
709 i = len(self.favoriteConfig.Entries) -1
710 self.favoriteConfig.Entries[i].name = ConfigText(default = "")
711 self.favoriteConfig.Entries[i].text = ConfigText(default = "")
712 self.favoriteConfig.Entries[i].type = ConfigText(default = "")
713 self.favoriteConfig.Entries[i].audio = ConfigText(default = "")
714 self.favoriteConfig.Entries[i].bitrate = ConfigText(default = "")
715 return self.favoriteConfig.Entries[i]
717 def initFavouriteConfig(self):
718 count = self.favoriteConfig.entriescount.value
722 self.initFavouriteEntryConfig()
725 def getSelectedItem(self):
728 sel = self["list"].l.getCurrentSelection()[0]
734 Pixmap.__init__(self)
735 self.picload = ePicLoad()
736 self.picload.PictureData.get().append(self.paintIconPixmapCB)
740 self.picload.setPara((self.instance.size().width(), self.instance.size().height(), 1, 1, False, 1, "#00000000"))
742 def paintIconPixmapCB(self, picInfo=None):
743 ptr = self.picload.getData()
745 self.instance.setPixmap(ptr.__deref__())
747 def updateIcon(self, filename):
748 self.picload.startDecode(filename)
750 class SHOUTcastList(GUIComponent, object):
751 def buildEntry(self, item):
752 width = self.l.getItemSize().width()
754 if self.mode == 0: # GENRELIST
755 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width, 20, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, item.name))
756 elif self.mode == 1: # STATIONLIST
757 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, item.name))
758 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 23, width, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, item.ct))
759 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 43, width / 2, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, _("Audio: %s") % item.mt))
760 res.append((eListboxPythonMultiContent.TYPE_TEXT, width / 2, 43, width / 2, 20, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, _("Bit rate: %s kbps") % item.br))
761 elif self.mode == 2: # FAVORITELIST
762 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, item.configItem.name.value))
763 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)))
764 if len(item.configItem.audio.value) != 0:
765 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 43, width / 2, 20, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, _("Audio: %s") % item.configItem.audio.value))
766 if len(item.configItem.bitrate.value) != 0:
767 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 GUIComponent.__init__(self)
772 self.l = eListboxPythonMultiContent()
773 self.l.setFont(0, gFont("Regular", 20))
774 self.l.setFont(1, gFont("Regular", 18))
775 self.l.setBuildFunc(self.buildEntry)
776 self.l.setItemHeight(22)
777 self.onSelectionChanged = [ ]
780 def setMode(self, mode):
782 if mode == 0: # GENRELIST
783 self.l.setItemHeight(22)
784 elif mode == 1 or mode == 2: # STATIONLIST OR FAVORITELIST
785 self.l.setItemHeight(63)
787 def connectSelChanged(self, fnc):
788 if not fnc in self.onSelectionChanged:
789 self.onSelectionChanged.append(fnc)
791 def disconnectSelChanged(self, fnc):
792 if fnc in self.onSelectionChanged:
793 self.onSelectionChanged.remove(fnc)
795 def selectionChanged(self):
796 for x in self.onSelectionChanged:
799 def getCurrent(self):
800 cur = self.l.getCurrentSelection()
801 return cur and cur[0]
803 GUI_WIDGET = eListbox
805 def postWidgetCreate(self, instance):
806 instance.setContent(self.l)
807 instance.selectionChanged.get().append(self.selectionChanged)
809 def preWidgetRemove(self, instance):
810 instance.setContent(None)
811 instance.selectionChanged.get().remove(self.selectionChanged)
813 def moveToIndex(self, index):
814 self.instance.moveSelectionTo(index)
816 def getCurrentIndex(self):
817 return self.instance.getCurrentIndex()
819 currentIndex = property(getCurrentIndex, moveToIndex)
820 currentSelection = property(getCurrent)
822 def setList(self, list):
825 class SHOUTcastLCDScreen(Screen):
827 <screen position="0,0" size="132,64" title="SHOUTcast">
828 <widget name="text1" position="4,0" size="132,14" font="Regular;12" halign="center" valign="center"/>
829 <widget name="text2" position="4,14" size="132,49" font="Regular;10" halign="center" valign="center"/>
832 def __init__(self, session, parent):
833 Screen.__init__(self, session)
834 self["text1"] = Label("SHOUTcast")
835 self["text2"] = Label("")
837 def setText(self, text):
838 self["text2"].setText(text)
841 class SHOUTcastSetup(Screen, ConfigListScreen):
844 <screen position="center,center" size="560,400" title="SHOUTcast Setup" >
845 <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
846 <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
847 <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
848 <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
849 <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" />
850 <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" />
851 <widget name="config" position="20,50" size="520,330" scrollbarMode="showOnDemand" />
854 def __init__(self, session):
855 Screen.__init__(self, session)
857 self["key_red"] = StaticText(_("Cancel"))
858 self["key_green"] = StaticText(_("OK"))
862 self.list.append(getConfigListEntry(_("Streaming rate:"), config.plugins.shoutcast.streamingrate))
863 self.list.append(getConfigListEntry(_("Reload station list:"), config.plugins.shoutcast.reloadstationlist))
864 self.list.append(getConfigListEntry(_("Rip to single file, name is timestamped"), config.plugins.shoutcast.riptosinglefile))
865 self.list.append(getConfigListEntry(_("Create a directory for each stream"), config.plugins.shoutcast.createdirforeachstream))
866 self.list.append(getConfigListEntry(_("Add sequence number to output file"), config.plugins.shoutcast.addsequenceoutputfile))
867 self.dirname = getConfigListEntry(_("Recording location:"), config.plugins.shoutcast.dirname)
868 self.list.append(self.dirname)
869 ConfigListScreen.__init__(self, self.list, session)
870 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
872 "green": self.keySave,
873 "cancel": self.keyClose,
874 "ok": self.keySelect,
878 cur = self["config"].getCurrent()
879 if cur == self.dirname:
880 self.session.openWithCallback(self.pathSelected,SHOUTcastStreamripperRecordingPath,config.plugins.shoutcast.dirname.value)
882 def pathSelected(self, res):
884 config.plugins.shoutcast.dirname.value = res
887 for x in self["config"].list:
893 for x in self["config"].list:
898 class SHOUTcastStreamripperRecordingPath(Screen):
899 skin = """<screen name="SHOUTcastStreamripperRecordingPath" position="center,center" size="560,320" title="Select record path for streamripper">
900 <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
901 <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
902 <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
903 <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
904 <widget name="target" position="0,60" size="540,22" valign="center" font="Regular;22" />
905 <widget name="filelist" position="0,100" zPosition="1" size="560,220" scrollbarMode="showOnDemand"/>
906 <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" />
907 <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" />
909 def __init__(self, session, initDir):
910 Screen.__init__(self, session)
911 inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"]
913 self["filelist"] = FileList(initDir, showDirectories = True, showFiles = False, inhibitMounts = inhibitMounts, inhibitDirs = inhibitDirs)
914 self["target"] = Label()
915 self["actions"] = ActionMap(["WizardActions", "DirectionActions", "ColorActions", "EPGSelectActions"],
927 self["key_red"] = StaticText(_("Cancel"))
928 self["key_green"] = StaticText(_("OK"))
934 self.close(self["filelist"].getSelection()[0])
937 self["filelist"].up()
941 self["filelist"].down()
945 self["filelist"].pageUp()
949 self["filelist"].pageDown()
953 if self["filelist"].canDescent():
954 self["filelist"].descent()
957 def updateTarget(self):
958 currFolder = self["filelist"].getSelection()[0]
959 if currFolder is not None:
960 self["target"].setText(currFolder)
962 self["target"].setText(_("Invalid Location"))