Patch of Mike Looijmans
[enigma2-plugins.git] / netcaster / src / bin / interface / shoutcast.py
1 from Plugins.Extensions.NETcaster.StreamInterface import StreamInterface
2 from Plugins.Extensions.NETcaster.StreamInterface import Stream
3 from Screens.ChoiceBox import ChoiceBox
4
5 class Interface(StreamInterface):
6     name= "listen to SHOUTcast Streams"
7     nameshort = "SHOUTcast"
8     description = "This is a Plugin to browse www.shoutcast.com and listen to webradios listed there."
9     def __init__(self,session,cbListLoaded=None):
10         StreamInterface.__init__(self,session,cbListLoaded=cbListLoaded)
11         self.genrefeed = GenreFeed()
12     
13     def getList(self):
14         glist=[]
15         #self.genrefeed.fetch_genres()
16         self.genrefeed.parse_genres()
17         for i in self.genrefeed.genre_list:            
18             glist.append((str(i),i))
19         self.session.openWithCallback(self.GenreSelected,ChoiceBox,_("select Genre to search for streams"),glist)
20
21     def GenreSelected(self,selectedGenre):
22         if selectedGenre is not None:
23             feed = ShoutcastFeed(selectedGenre[1])
24             #feed.fetch_stations()
25             feed.parse_stations()
26             self.list=[]
27             for station in feed.station_list:
28                 stream = Stream(str(station['Name']),"Bitrate: "+str(station['Bitrate'])+", Type: "+str(station['MimeType']),str(station['PLS_URL']),type="pls")
29                 self.list.append(stream)
30         self.OnListLoaded()
31
32 ####################################################
33 # feeds.py - Gets the current listings of Shoutcast stations
34 # $Id$
35 # Copyright (C) 2005-2006 Matthew Schick <matt@excentral.org>
36
37 # This library is free software; you can redistribute it and/or
38 # modify it under the terms of the GNU Lesser General Public
39 # License as published by the Free Software Foundation; either
40 # version 2.1 of the License, or (at your option) any later version.
41
42 # This library is distributed in the hope that it will be useful,
43 # but WITHOUT ANY WARRANTY; without even the implied warranty of
44 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
45 # Lesser General Public License for more details.
46
47 # You should have received a copy of the GNU Lesser General Public
48 # License along with this library; if not, write to the Free Software
49 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
50
51 from cPickle import dump, load
52 from urllib import FancyURLopener
53 from xml.sax import parseString
54 from xml.sax.handler import ContentHandler
55 from os import stat, mkdir
56 from os.path import dirname,isdir
57 import time
58 from stat import ST_MTIME
59
60 tmpxml='shout.xml'
61 DEBUG=0
62
63 def write_cache(cache_file, cache_data):
64     """
65     Does a cPickle dump
66     """
67     if not isdir( dirname(cache_file) ):
68         try:
69             mkdir( dirname(cache_file) )
70         except OSError:
71             print dirname(cache_file), 'is a file'
72     fd = open(cache_file, 'w')
73     dump(cache_data, fd, -1)
74     fd.close()
75
76 def cacheTime(cache_file):
77     """
78     Returns None if no cache file, its MTIME otherwise
79     """
80     try:
81         mtime = stat(cache_file)[ST_MTIME]
82     except:
83         return None
84     return mtime
85
86 def load_cache(cache_file):
87     """
88     Does a cPickle load
89     """
90     fd = open(cache_file)
91     cache_data = load(fd)
92     fd.close()
93     return cache_data
94
95 class StationParser(ContentHandler):
96     """
97     SAX handler for xml feed, not for public consumption
98     """
99     def __init__(self,min_bitrate):
100         self.isStationList = False
101         self.isTuneIn = False
102         self.isStation = False
103         self.station_list = []
104         self.min_bitrate = min_bitrate
105         self.mimeType = ''
106         self.Id = ''
107         self.Name = ''
108         self.Bitrate = ''
109         self.nowPlaying = ''
110         self.Listeners = ''
111         self.stationUrl = ''
112         self.Genre = ''
113         self.count = 0
114         self.shoutUrl = 'http://www.shoutcast.com'
115     def startElement(self, name, attrs):
116         if name == 'stationlist':
117             self.isStationList = True
118         if name == 'tunein':
119             self.isTuneIn = True
120             self.baseUrl = attrs.get('base', None)
121         if name == 'station':
122             self.isStation = True
123             self.Name = attrs.get('name', None)
124             self.mimeType = attrs.get('mt', None)
125             self.Id = attrs.get('id', None)
126             self.Bitrate = attrs.get('br', None)
127             self.nowPlaying = attrs.get('ct', None)
128             self.Listeners = attrs.get('lc', None)
129             self.Genre = attrs.get('genre', None)
130     def endElement(self,name):
131         if name == 'station':
132             self.isStation = False
133         if name == 'tunein':
134             self.isTuneIn = False
135         if name == 'station':
136             self.isStation = False
137             if int(self.Bitrate) >= self.min_bitrate:
138                 self.stationUrl = self.shoutUrl + self.baseUrl + '?id=' + self.Id
139                 self.station_list.append({'Name':self.Name.encode("utf-8"), 'PLS_URL':self.stationUrl.encode("utf-8"), 'NowPlaying':self.nowPlaying.encode("utf-8"), 'Listeners':self.Listeners.encode("utf-8"), 'Bitrate':self.Bitrate.encode("utf-8"), 'MimeType':self.mimeType.encode("utf-8"), 'Genres': self.Genre.encode("utf-8")})
140                 self.count += 1
141         if name == 'stationlist':
142             self.isStationList = False
143             if DEBUG == 1:
144                 print 'Parsed ', self.count, ' stations'
145
146 class GenreParse(ContentHandler):
147     def __init__( self ):
148         self.isGenre = False
149         self.isGenreList = False
150         self.genreList = []
151     def startElement( self, name, attrs ):
152         if name == 'genrelist':
153             self.isGenreList = True
154         if name == 'genre':
155             self.isGenre == True
156             self.genre_name = attrs.get( 'name', None )
157     def endElement( self, name ):
158         if name == 'genre':
159             self.isGenre = False
160             self.genreList.append( self.genre_name.encode("utf-8") )
161         if name == 'genrelist':
162             self.isGenreList = False
163
164 class GenreFeed:
165     def __init__(self, cache_ttl=3600, cache_dir = '/tmp/pyshout_cache'):
166         self.cache_ttl = cache_ttl
167         self.cache_file = cache_dir + '/genres.cache'
168         self.genre_list = ['Sorry, failed to load', '...try again later', 'Rock', 'Pop', 'Alternative']
169     def fetch_genres(self):
170         """
171         Grabs genres and returns tuple of genres
172         """
173         self.genre_url = 'http://www.shoutcast.com/sbin/newxml.phtml'
174         self.urlhandler = FancyURLopener()
175         self.fd = self.urlhandler.open(self.genre_url)
176         self.genre = self.fd.read()
177         self.fd.close()
178         return self.genre
179
180     def parse_genres(self):
181         ct = None
182         if self.cache_ttl:
183             ct = cacheTime(self.cache_file)
184             try:
185                 self.genre_list = load_cache(self.cache_file)
186             except:
187                 ct = None
188         if not ct or (time.time() - ct) > self.cache_ttl:
189             if DEBUG == 1:
190                 print 'Getting fresh feed'
191             try:
192                 parseXML = GenreParse()
193                 self.genres = self.fetch_genres()
194                 parseString( self.genres, parseXML )
195                 self.genre_list = parseXML.genreList
196                 write_cache(self.cache_file, self.genre_list)
197             except:
198                 print "Failed to get genres from server, sorry."
199         return self.genre_list
200
201 class ShoutcastFeed:
202     def __init__(self, genre, min_bitrate=128, cache_ttl=600, cache_dir='/tmp/pyshout_cache'):
203         """
204         Parses the xml feed and spits out a list of dictionaries with the station info
205         keyed by genre. Params are as follows:
206         min_bitrate - 128 default, Minimum bitrate filter
207         cache_ttl - 600 default, 0 disables, Seconds cache is considered valid
208         cache_dir - /tmp/pyshout_cache default, Path to cache directory
209         """
210         self.min_bitrate = min_bitrate
211         self.cache_ttl = cache_ttl
212         self.genre = genre
213         self.cache_file = cache_dir + '/' + self.genre + '.pickle'
214         self.station_list = []
215
216     def fetch_stations(self):
217         """
218         Grabs the xml list of stations from the shoutcast server
219         """
220         self.shout_url='http://www.shoutcast.com/sbin/newxml.phtml?genre=' + self.genre
221         self.urlhandler = FancyURLopener()
222         self.fd = self.urlhandler.open(self.shout_url)
223         self.stations = self.fd.read()
224         self.fd.close()
225         return self.stations
226
227     def parse_stations(self):
228         ct = None
229         if self.cache_ttl:
230             ct = cacheTime(self.cache_file)
231         if ct:
232             try:
233                 self.station_list = load_cache(self.cache_file)
234             except:
235                 print "Failed to load cache."
236         if not ct or (time.time() - ct) > self.cache_ttl:
237             try:
238                 parseXML = StationParser(self.min_bitrate)
239                 self.stations = self.fetch_stations()
240                 parseString(self.stations, parseXML)
241                 self.station_list = parseXML.station_list
242                 write_cache(self.cache_file, self.station_list)
243             except:
244                 print "Failed to get a new station list, sorry."
245         return self.station_list