~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/protocols/shoutcast.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

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