| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 | |
| 5 """Chop up shoutcast stream into MP3s and metadata, if available.""" | |
| 6 | |
| 7 from twisted.web import http | |
| 8 from twisted import copyright | |
| 9 | |
| 10 | |
| 11 class ShoutcastClient(http.HTTPClient): | |
| 12 """Shoutcast HTTP stream. | |
| 13 | |
| 14 Modes can be 'length', 'meta' and 'mp3'. | |
| 15 | |
| 16 See http://www.smackfu.com/stuff/programming/shoutcast.html | |
| 17 for details on the protocol. | |
| 18 """ | |
| 19 | |
| 20 userAgent = "Twisted Shoutcast client " + copyright.version | |
| 21 | |
| 22 def __init__(self, path="/"): | |
| 23 self.path = path | |
| 24 self.got_metadata = False | |
| 25 self.metaint = None | |
| 26 self.metamode = "mp3" | |
| 27 self.databuffer = "" | |
| 28 | |
| 29 def connectionMade(self): | |
| 30 self.sendCommand("GET", self.path) | |
| 31 self.sendHeader("User-Agent", self.userAgent) | |
| 32 self.sendHeader("Icy-MetaData", "1") | |
| 33 self.endHeaders() | |
| 34 | |
| 35 def lineReceived(self, line): | |
| 36 # fix shoutcast crappiness | |
| 37 if not self.firstLine and line: | |
| 38 if len(line.split(": ", 1)) == 1: | |
| 39 line = line.replace(":", ": ", 1) | |
| 40 http.HTTPClient.lineReceived(self, line) | |
| 41 | |
| 42 def handleHeader(self, key, value): | |
| 43 if key.lower() == 'icy-metaint': | |
| 44 self.metaint = int(value) | |
| 45 self.got_metadata = True | |
| 46 | |
| 47 def handleEndHeaders(self): | |
| 48 # Lets check if we got metadata, and set the | |
| 49 # appropriate handleResponsePart method. | |
| 50 if self.got_metadata: | |
| 51 # if we have metadata, then it has to be parsed out of the data stre
am | |
| 52 self.handleResponsePart = self.handleResponsePart_with_metadata | |
| 53 else: | |
| 54 # otherwise, all the data is MP3 data | |
| 55 self.handleResponsePart = self.gotMP3Data | |
| 56 | |
| 57 def handleResponsePart_with_metadata(self, data): | |
| 58 self.databuffer += data | |
| 59 while self.databuffer: | |
| 60 stop = getattr(self, "handle_%s" % self.metamode)() | |
| 61 if stop: | |
| 62 return | |
| 63 | |
| 64 def handle_length(self): | |
| 65 self.remaining = ord(self.databuffer[0]) * 16 | |
| 66 self.databuffer = self.databuffer[1:] | |
| 67 self.metamode = "meta" | |
| 68 | |
| 69 def handle_mp3(self): | |
| 70 if len(self.databuffer) > self.metaint: | |
| 71 self.gotMP3Data(self.databuffer[:self.metaint]) | |
| 72 self.databuffer = self.databuffer[self.metaint:] | |
| 73 self.metamode = "length" | |
| 74 else: | |
| 75 return 1 | |
| 76 | |
| 77 def handle_meta(self): | |
| 78 if len(self.databuffer) >= self.remaining: | |
| 79 if self.remaining: | |
| 80 data = self.databuffer[:self.remaining] | |
| 81 self.gotMetaData(self.parseMetadata(data)) | |
| 82 self.databuffer = self.databuffer[self.remaining:] | |
| 83 self.metamode = "mp3" | |
| 84 else: | |
| 85 return 1 | |
| 86 | |
| 87 def parseMetadata(self, data): | |
| 88 meta = [] | |
| 89 for chunk in data.split(';'): | |
| 90 chunk = chunk.strip().replace("\x00", "") | |
| 91 if not chunk: | |
| 92 continue | |
| 93 key, value = chunk.split('=', 1) | |
| 94 if value.startswith("'") and value.endswith("'"): | |
| 95 value = value[1:-1] | |
| 96 meta.append((key, value)) | |
| 97 return meta | |
| 98 | |
| 99 def gotMetaData(self, metadata): | |
| 100 """Called with a list of (key, value) pairs of metadata, | |
| 101 if metadata is available on the server. | |
| 102 | |
| 103 Will only be called on non-empty metadata. | |
| 104 """ | |
| 105 raise NotImplementedError, "implement in subclass" | |
| 106 | |
| 107 def gotMP3Data(self, data): | |
| 108 """Called with chunk of MP3 data.""" | |
| 109 raise NotImplementedError, "implement in subclass" | |
| 110 | |
| 111 | |
| 112 if __name__ == '__main__': | |
| 113 class Test(ShoutcastClient): | |
| 114 def gotMetaData(self, data): print "meta:", data | |
| 115 def gotMP3Data(self, data): pass | |
| 116 | |
| 117 from twisted.internet import protocol, reactor | |
| 118 import sys | |
| 119 protocol.ClientCreator(reactor, Test).connectTCP(sys.argv[1], int(sys.argv[2
])) | |
| 120 reactor.run() | |
| OLD | NEW |