Relicense bindings as MIT License
[deb_shairplay.git] / src / bindings / python / Shairplay.py
CommitLineData
1f0de05c 1# coding=utf-8
094e4ad1 2'''
6b27160f 3Copyright (C) 2012 Juho Vähä-Herttua
094e4ad1 4
6b27160f
JVH
5Permission is hereby granted, free of charge, to any person obtaining
6a copy of this software and associated documentation files (the
7"Software"), to deal in the Software without restriction, including
8without limitation the rights to use, copy, modify, merge, publish,
9distribute, sublicense, and/or sell copies of the Software, and to
10permit persons to whom the Software is furnished to do so, subject to
11the following conditions:
094e4ad1 12
6b27160f
JVH
13The above copyright notice and this permission notice shall be included
14in all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
094e4ad1
JVH
23'''
24
8be1424a
JVH
25import os
26import sys
27import platform
28
29from ctypes import *
30
31audio_init_prototype = CFUNCTYPE(py_object, c_void_p, c_int, c_int, c_int)
32audio_set_volume_prototype = CFUNCTYPE(None, c_void_p, c_void_p, c_float)
33audio_process_prototype = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p, c_int)
34audio_flush_prototype = CFUNCTYPE(None, c_void_p, c_void_p)
35audio_destroy_prototype = CFUNCTYPE(None, c_void_p, c_void_p)
36
37class RaopNativeCallbacks(Structure):
38 _fields_ = [("cls", py_object),
39 ("audio_init", audio_init_prototype),
40 ("audio_set_volume", audio_set_volume_prototype),
41 ("audio_process", audio_process_prototype),
42 ("audio_flush", audio_flush_prototype),
43 ("audio_destroy", audio_destroy_prototype)]
44
45def LoadShairplay(path):
46 if sys.maxsize < 2**32:
47 libname = "shairplay32"
48 else:
49 libname = "shairplay64"
50
51 if platform.system() == "Windows":
52 libname = libname + ".dll"
53 elif platform.system() == "Darwin":
54 libname = "lib" + libname + ".dylib"
55 else:
56 libname = "lib" + libname + ".so"
57
58 try:
59 fullpath = os.path.join(path, libname)
60 libshairplay = cdll.LoadLibrary(fullpath)
61 except:
62 raise RuntimeError("Couldn't load shairplay library " + libname)
63
64 # Initialize dnssd related functions
65 libshairplay.dnssd_init.restype = c_void_p
094e4ad1 66 libshairplay.dnssd_init.argtypes = [POINTER(c_int)]
8be1424a 67 libshairplay.dnssd_register_raop.restype = c_int
094e4ad1 68 libshairplay.dnssd_register_raop.argtypes = [c_void_p, c_char_p, c_ushort, POINTER(c_char), c_int]
8be1424a 69 libshairplay.dnssd_register_airplay.restype = c_int
094e4ad1 70 libshairplay.dnssd_register_airplay.argtypes = [c_void_p, c_char_p, c_ushort, POINTER(c_char), c_int]
8be1424a
JVH
71 libshairplay.dnssd_unregister_raop.restype = None
72 libshairplay.dnssd_unregister_raop.argtypes = [c_void_p]
73 libshairplay.dnssd_unregister_airplay.restype = None
74 libshairplay.dnssd_unregister_airplay.argtypes = [c_void_p]
75 libshairplay.dnssd_destroy.restype = None
76 libshairplay.dnssd_destroy.argtypes = [c_void_p]
77
78 # Initialize raop related functions
79 libshairplay.raop_init.restype = c_void_p
80 libshairplay.raop_init.argtypes = [POINTER(RaopNativeCallbacks), c_char_p]
81 libshairplay.raop_start.restype = c_int
82 libshairplay.raop_start.argtypes = [c_void_p, POINTER(c_ushort), POINTER(c_char), c_int]
83 libshairplay.raop_stop.restype = None
84 libshairplay.raop_stop.argtypes = [c_void_p]
85 libshairplay.raop_destroy.restype = None
86 libshairplay.raop_destroy.argtypes = [c_void_p]
87
88 return libshairplay
89
90RSA_KEY = """
91-----BEGIN RSA PRIVATE KEY-----
92MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt
93wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U
94wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf
95/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/
96UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW
97BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa
98LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5
99NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm
100lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz
101aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu
102a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM
103oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z
104oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+
105k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL
106AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA
107cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf
10854PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov
10917fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc
1101JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI
111LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ
1122gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=
113-----END RSA PRIVATE KEY-----
114"""
115
116class RaopCallbacks:
117 def audio_init(self, bits, channels, samplerate):
118 raise NotImplementedError()
119
120 def audio_set_volume(self, session, volume):
121 pass
122
123 def audio_process(self, session, buffer):
124 raise NotImplementedError()
125
126 def audio_flush(self, session):
127 pass
128
129 def audio_destroy(self, session):
130 pass
131
132class RaopService:
133 def audio_init_cb(self, cls, bits, channels, samplerate):
134 session = self.callbacks.audio_init(bits, channels, samplerate)
1f0de05c
JVH
135 self.sessions.append(session)
136 return session
8be1424a
JVH
137
138 def audio_set_volume_cb(self, cls, sessionptr, volume):
139 session = cast(sessionptr, py_object).value
140 self.callbacks.audio_set_volume(session, volume)
141
142 def audio_process_cb(self, cls, sessionptr, buffer, buflen):
143 session = cast(sessionptr, py_object).value
144 strbuffer = string_at(buffer, buflen)
145 self.callbacks.audio_process(session, strbuffer)
146
147 def audio_flush_cb(self, cls, sessionptr):
148 session = cast(sessionptr, py_object).value
149 self.callbacks.audio_flush(session)
150
151 def audio_destroy_cb(self, cls, sessionptr):
152 session = cast(sessionptr, py_object).value
153 self.callbacks.audio_destroy(session)
1f0de05c
JVH
154 if session in self.sessions:
155 self.sessions.remove(session)
8be1424a
JVH
156
157
158 def __init__(self, libshairplay, callbacks):
159 self.libshairplay = libshairplay
160 self.callbacks = callbacks
1f0de05c 161 self.sessions = []
8be1424a
JVH
162 self.instance = None
163
164 # We need to hold a reference to native_callbacks
165 self.native_callbacks = RaopNativeCallbacks()
166 self.native_callbacks.audio_init = audio_init_prototype(self.audio_init_cb)
167 self.native_callbacks.audio_set_volume = audio_set_volume_prototype(self.audio_set_volume_cb)
168 self.native_callbacks.audio_process = audio_process_prototype(self.audio_process_cb)
169 self.native_callbacks.audio_flush = audio_flush_prototype(self.audio_flush_cb)
170 self.native_callbacks.audio_destroy = audio_destroy_prototype(self.audio_destroy_cb)
171
172 # Initialize the raop instance with our callbacks
173 self.instance = self.libshairplay.raop_init(pointer(self.native_callbacks), RSA_KEY)
174 if self.instance == None:
175 raise RuntimeError("Initializing library failed")
176
177 def __del__(self):
178 if self.instance != None:
179 self.libshairplay.raop_destroy(self.instance)
180 self.instance = None
181
182 def start(self, port, hwaddrstr):
183 port = c_ushort(port)
184 hwaddr = create_string_buffer(hwaddrstr, len(hwaddrstr))
185
186 ret = self.libshairplay.raop_start(self.instance, pointer(port), hwaddr, c_int(len(hwaddr)))
187 if ret < 0:
188 raise RuntimeError("Starting RAOP instance failed")
189 return port.value
190
191 def stop(self):
192 self.libshairplay.raop_stop(self.instance)
193
194class DnssdService:
195 def __init__(self, libshairplay):
196 self.libshairplay = libshairplay
197 self.instance = None
198
8be1424a
JVH
199 error = c_int(0)
200
094e4ad1 201 self.instance = self.libshairplay.dnssd_init(pointer(error))
8be1424a
JVH
202 if self.instance == None:
203 raise RuntimeError("Initializing library failed: " + str(error.value))
204
205 def __del__(self):
206 if self.instance != None:
207 self.libshairplay.dnssd_destroy(self.instance)
208 self.instance = None
209
094e4ad1
JVH
210 def register_raop(self, name, port, hwaddrstr):
211 hwaddr = create_string_buffer(hwaddrstr, len(hwaddrstr))
212 self.libshairplay.dnssd_register_raop(self.instance, name, c_ushort(port), hwaddr, len(hwaddr))
8be1424a
JVH
213
214 def unregister_raop(self):
215 self.libshairplay.dnssd_unregister_raop(self.instance)
216
094e4ad1
JVH
217 def register_airplay(self, name, port, hwaddrstr):
218 hwaddr = create_string_buffer(hwaddrstr, len(hwaddrstr))
219 self.libshairplay.dnssd_register_airplay(self.instance, name, c_ushort(port), hwaddr, len(hwaddr))
8be1424a
JVH
220
221 def unregister_airplay(self):
222 self.libshairplay.dnssd_unregister_airplay(self.instance)
223