class PlsPlaylistDecoder: def __init__(self): self.log = Logger() self.log.debug('PLS playlist decoder') def isStreamValid(self, contentType, firstBytes): if 'audio/x-scpls' in contentType or \ 'application/pls+xml' in contentType or \ firstBytes.strip().lower().startswith(b'[playlist]'): self.log.info('Stream is readable by PLS Playlist Decoder') return True else: return False def extractPlaylist(self, url): self.log.info('Downloading playlist...') req = UrlRequest(url) req.add_header('User-Agent', USER_AGENT) f = urlUrlopen(req) str = f.read() f.close() self.log.info('Playlist downloaded') self.log.info('Decoding playlist...') playlist = [] lines = str.splitlines() for line in lines: if line.startswith(b"File") == True: list = line.split(b"=", 1) playlist.append(list[1]) return playlist
class M3uPlaylistDecoder: def __init__(self): self.log = Logger() def isStreamValid(self, contentType, firstBytes): if 'audio/mpegurl' in contentType or 'audio/x-mpegurl' in contentType: self.log.info('Stream is readable by M3U Playlist Decoder') return True else: lines = firstBytes.splitlines() for line in lines: if line.startswith(b"http://"): return True return False def extractPlaylist(self, url): self.log.info('M3u: downloading playlist...') req = UrlRequest(url) req.add_header('User-Agent', USER_AGENT) f = urlUrlopen(req) str = f.read() f.close() self.log.info('M3U: playlist downloaded, decoding... ') lines = str.splitlines() playlist = [] for line in lines: if line.startswith(b"#") == False and len(line) > 0: playlist.append(line) return playlist
class XspfPlaylistDecoder: def __init__(self): self.log = Logger() def isStreamValid(self, contentType, firstBytes): if 'application/xspf+xml' in contentType: self.log.info('Stream is readable by XSPF Playlist Decoder') return True else: return False def extractPlaylist(self, url): self.log.info('XSPF: downloading playlist...') req = UrlRequest(url) req.add_header('User-Agent', USER_AGENT) f = urlUrlopen(req) str = f.read() f.close() self.log.info('XSPF: playlist downloaded, decoding...') root = ET.parse(BytesIO(str)) ns = {'xspf': 'http://xspf.org/ns/0/'} elements = root.findall(".//xspf:track/xspf:location", ns) result = [] for r in elements: result.append(r.text) return result
class AsfPlaylistDecoder: def __init__(self): self.log = Logger() def isStreamValid(self, contentType, firstBytes): if 'video/x-ms-asf' in contentType and \ firstBytes.strip().lower().startswith('b[reference]'): self.log.info('Stream is readable by ASF Playlist Decoder') return True else: return False def extractPlaylist(self, url): self.log.info('ASF: downloading playlist..') req = UrlRequest(url) req.add_header('User-Agent', USER_AGENT) f = urlUrlopen(req) str = f.read() f.close() self.log.info('ASF: playlist downloaded, decoding...') playlist = [] lines = str.splitlines() for line in lines: if line.startswith(b"Ref"): list = line.split(b"=", 1) tmp = list[1].strip() if tmp.endswith(b"?MSWMExt=.asf"): playlist.append(tmp.replace(b"http", b"mms")) else: playlist.append(tmp) return playlist
class RamPlaylistDecoder: def __init__(self): self.log = Logger() def isStreamValid(self, contentType, firstBytes): if 'audio/x-pn-realaudio' in contentType or \ 'audio/vnd.rn-realaudio' in contentType: self.log.info('Stream is readable by RAM Playlist Decoder') return True else: return False def extractPlaylist(self, url): self.log.info('RAM: Downloading playlist...') req = UrlRequest(url) req.add_header('User-Agent', USER_AGENT) f = urUrlopen(req) str = f.read() f.close() self.log.info('RAM Playlist downloaded, decoding...') lines = str.splitlines() playlist = [] for line in lines: if len(line) > 0 and not line.startswith(b"#"): tmp = line.strip() if len(tmp) > 0: playlist.append(line.strip()) return playlist
class AsxPlaylistDecoder: def __init__(self): self.log = Logger() def isStreamValid(self, contentType, firstBytes): if ('audio/x-ms-wax' in contentType or \ 'video/x-ms-wvx' in contentType or \ 'video/x-ms-asf' in contentType or \ 'video/x-ms-wmv' in contentType) and \ firstBytes.strip().lower().startswith(b'<asx'): self.log.info('Stream is readable by ASX Playlist Decoder') return True else: return False def extractPlaylist(self, url): self.log.info('ASX: Downloading playlist...') req = UrlRequest(url) req.add_header('User-Agent', USER_AGENT) f = urlUrlopen(req) str = f.read() f.close() self.log.info('ASX: playlist downloaded, decoding...') try: root = ET.parse(BytesIO(str)) except: # Last ditch: try to fix docs with mismatched tag name case str = re.sub('''<([A-Za-z0-9/]+)''', \ lambda m: "<" + m.group(1).lower(), str) root = ET.parse(BytesIO(str)) #ugly hack to normalize the XML for element in root.iter(): tmp = element.tag element.tag = tmp.lower() keys = element.attrib.keys() for key in keys: element.attrib[key.lower()] = element.attrib[key] elts = root.findall(".//ref/[@href]") result = [] for elt in elts: tmp = elt.attrib['href'] if (tmp.endswith("?MSWMExt=.asf")): tmp = tmp.replace("http", "mms") result.append(tmp) return result
class StreamDecoder: def __init__(self, cfg_provider): plsDecoder = PlsPlaylistDecoder() m3uDecoder = M3uPlaylistDecoder() asxDecoder = AsxPlaylistDecoder() xspfDecoder = XspfPlaylistDecoder() asfDecoder = AsfPlaylistDecoder() ramDecoder = RamPlaylistDecoder() self.log = Logger() self.decoders = [ plsDecoder, asxDecoder, asfDecoder, xspfDecoder, ramDecoder, m3uDecoder ] self.url_timeout = None try: self.url_timeout = cfg_provider.getConfigValue("url_timeout") if (self.url_timeout == None): self.log.warn("Couldn't find url_timeout configuration") self.url_timeout = 100 cfg_provider.setConfigValue("url_timeout", str(self.url_timeout)) except Exception as e: self.log.warn("Couldn't find url_timeout configuration") self.url_timeout = 100 cfg_provider.setConfigValue("url_timeout", str(self.url_timeout)) self.log.info('Using url timeout = %s' % str(self.url_timeout)) def getMediaStreamInfo(self, url): if type(url) != type(u""): url = url.decode('utf-8') if url.startswith("http") == False: self.log.info('Not an HTTP url. Maybe direct stream...') return UrlInfo(url, False, None) self.log.info('Requesting stream... %s' % url) req = UrlRequest(url) req.add_header('User-Agent', USER_AGENT) try: opener = urlBuild_opener( DummyMMSHandler(), HTTPSHandler(context=my_ssl_create_unverified_context())) f = opener.open(req, timeout=float(self.url_timeout)) except HTTPError as e: self.log.warn('HTTP Error for %s: %s' % (url, e)) return None except URLError as e: self.log.info('URLError for %s: %s ' % (url, e)) if str(e.reason).startswith('MMS REDIRECT'): newurl = e.reason.split("MMS REDIRECT:", 1)[1] self.log.info('Found mms redirect for: %s' % newurl) return UrlInfo(newurl, False, None) else: return None except BadStatusLine as e: if str(e).startswith('ICY 200'): self.log.info('Found ICY stream') return UrlInfo(url, False, None) else: return None except Exception as e: print('%s: for %s: %s' % (type(e), url, e), file=sys.stderr) self.log.warn('%s: for %s: %s' % (type(e), url, e)) return None metadata = f.info() firstbytes = f.read(500) f.close() try: contentType = metadata["content-type"] self.log.info('Content-Type: %s' % contentType) except Exception as e: self.log.info("Couldn't read content-type. Maybe direct stream...") return UrlInfo(url, False, None) for decoder in self.decoders: self.log.info('Checking decoder') if decoder.isStreamValid(contentType, firstbytes): return UrlInfo(url, True, contentType, decoder) # no playlist decoder found. Maybe a direct stream self.log.info( 'No playlist decoder could handle the stream. Maybe direct stream...' ) return UrlInfo(url, False, contentType) def getPlaylist(self, urlInfo): return urlInfo.getDecoder().extractPlaylist(urlInfo.getUrl())