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