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