python-osc 1.10.0__py3-none-any.whl → 1.10.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_osc-1.10.0.dist-info → python_osc-1.10.3.dist-info}/METADATA +3 -3
- python_osc-1.10.3.dist-info/RECORD +36 -0
- {python_osc-1.10.0.dist-info → python_osc-1.10.3.dist-info}/WHEEL +1 -1
- pythonosc/dispatcher.py +70 -49
- pythonosc/osc_bundle.py +6 -0
- pythonosc/osc_message_builder.py +20 -9
- pythonosc/osc_server.py +26 -0
- pythonosc/osc_tcp_server.py +68 -164
- pythonosc/parsing/framing.py +166 -0
- pythonosc/parsing/osc_types.py +13 -7
- pythonosc/tcp_client.py +70 -128
- pythonosc/test/parsing/test_framing.py +158 -0
- pythonosc/test/parsing/test_osc_types.py +28 -0
- pythonosc/test/test_dispatcher.py +82 -1
- pythonosc/test/test_empty_string_messages.py +87 -0
- pythonosc/test/test_osc_bundle.py +21 -2
- pythonosc/test/test_osc_message.py +5 -0
- pythonosc/test/test_osc_server.py +55 -1
- pythonosc/test/test_osc_tcp_server.py +45 -27
- pythonosc/test/test_tcp_client.py +2 -1
- pythonosc/test/test_udp_client.py +25 -0
- pythonosc/udp_client.py +24 -9
- python_osc-1.10.0.dist-info/RECORD +0 -33
- /pythonosc/{slip.py → parsing/slip.py} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: python-osc
|
|
3
|
-
Version: 1.10.
|
|
3
|
+
Version: 1.10.3
|
|
4
4
|
Summary: Open Sound Control server and client implementations in pure Python
|
|
5
5
|
Keywords: osc,sound,midi,music
|
|
6
6
|
Author: attwad
|
|
@@ -114,7 +114,7 @@ Simple client
|
|
|
114
114
|
help="The port the OSC server is listening on")
|
|
115
115
|
args = parser.parse_args()
|
|
116
116
|
|
|
117
|
-
client = udp_client.SimpleUDPClient(args.ip, args.port)
|
|
117
|
+
client = udp_client.SimpleUDPClient(args.ip, args.port, timeout=10)
|
|
118
118
|
|
|
119
119
|
for x in range(10):
|
|
120
120
|
client.send_message("/filter", random.random())
|
|
@@ -158,7 +158,7 @@ Simple server
|
|
|
158
158
|
dispatcher.map("/logvolume", print_compute_handler, "Log volume", math.log)
|
|
159
159
|
|
|
160
160
|
server = osc_server.ThreadingOSCUDPServer(
|
|
161
|
-
(args.ip, args.port), dispatcher)
|
|
161
|
+
(args.ip, args.port), dispatcher, timeout=10)
|
|
162
162
|
print("Serving on {}".format(server.server_address))
|
|
163
163
|
server.serve_forever()
|
|
164
164
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
pythonosc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pythonosc/dispatcher.py,sha256=L1d_jm33uwsSHrNlfaLhTXflc70apcK5FwRTJTuEf3Q,15051
|
|
3
|
+
pythonosc/osc_bundle.py,sha256=k8CR9K-MFr8iKj3pYSj72PE2nyhZsIle-GnVpqSrcvM,4151
|
|
4
|
+
pythonosc/osc_bundle_builder.py,sha256=1m1K1bC3NIbUNjNo8lRFQ-Hciyq4hzWEPQm48c1W8DA,2113
|
|
5
|
+
pythonosc/osc_message.py,sha256=GlWwFMhquvt6JuKo8hC4q_S9aSHYTGsfTUUPvO-5uaQ,4797
|
|
6
|
+
pythonosc/osc_message_builder.py,sha256=CHgpPdU9mfTZ52Dt10J0nrvG85OzP474lAhzKGSU_dE,8080
|
|
7
|
+
pythonosc/osc_packet.py,sha256=72LVRxaPaTwkkih7tsPWgWJlyeHxO7xt3SOOK8YMh50,2778
|
|
8
|
+
pythonosc/osc_server.py,sha256=j-cwrZYeiZJTrbYXaW01JiH0H0IFRQLqXrfv0BsdxTY,7445
|
|
9
|
+
pythonosc/osc_tcp_server.py,sha256=Bh_WJHjrtWMtU1gOpY7X1jZEpZNJN5hztllx_UoHT1M,7141
|
|
10
|
+
pythonosc/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
pythonosc/parsing/framing.py,sha256=P31sXt9_mO9bUUNjd6bBlS7T3EGUEh129PZ93QzeOhs,4492
|
|
12
|
+
pythonosc/parsing/ntp.py,sha256=9lpFonFynAVYtW5J2ttgnbjM0ej8mTl0BFsm82Ud8Ek,1877
|
|
13
|
+
pythonosc/parsing/osc_types.py,sha256=-YfmtM0q-WVliJwXJaYEcY5MfGYzp0xyTu2eZ6z9GSE,15008
|
|
14
|
+
pythonosc/parsing/slip.py,sha256=KhEBQ3v21A3liulQWMiBYw3oVY_BtEb1wZj1q3tnrIk,3676
|
|
15
|
+
pythonosc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
pythonosc/tcp_client.py,sha256=ywJaAMWSF_a3kuNAYEe_of-jccwXsnOHM84G7cuHDmc,8039
|
|
17
|
+
pythonosc/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
pythonosc/test/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
pythonosc/test/parsing/test_framing.py,sha256=IYvt_FgLUkeFabbleSuxVqcMiRToy38ugxYaMBTcL9o,5048
|
|
20
|
+
pythonosc/test/parsing/test_ntp.py,sha256=KY4XiArsNZtni46Glq0F6dR3psVDbOROdOjFupTWVuI,605
|
|
21
|
+
pythonosc/test/parsing/test_osc_types.py,sha256=TQz0-yCT7FCN3DjpR3e22tiLpX829rGeogj1tuim-P8,13805
|
|
22
|
+
pythonosc/test/test_dispatcher.py,sha256=C0TsHqoino_2_Dnu10WCBROtB9zi4a2pjft4NvFO7Ps,9367
|
|
23
|
+
pythonosc/test/test_empty_string_messages.py,sha256=F6OpZP5xJP3RY_leWigJd6MdQF6Cdm_v5js3_U8Ba6Q,3768
|
|
24
|
+
pythonosc/test/test_osc_bundle.py,sha256=eXsIl_Jpo9lVmcDVG9o4rWlpTyo7XfBG294_uXp-6Vg,4837
|
|
25
|
+
pythonosc/test/test_osc_bundle_builder.py,sha256=7P2DPBun2TrT_4gUq2_haEJiNC8dbGHwzaRF5MJzJHs,1534
|
|
26
|
+
pythonosc/test/test_osc_message.py,sha256=sDU73FGbEKL5tLYyD04FUsC4aJ1jWxJJmoAaRoWTuuI,13250
|
|
27
|
+
pythonosc/test/test_osc_message_builder.py,sha256=RaFPH9biU6P7LZ-p3N7H5Cfg5w6d2BBxVrwGA9EWwCw,3982
|
|
28
|
+
pythonosc/test/test_osc_packet.py,sha256=GCK7mYKv0bzOwCuhwbA7eaxPOjsn-pTzTbpMzWTXo-s,2163
|
|
29
|
+
pythonosc/test/test_osc_server.py,sha256=MtPJ6ZdEIArRrZxkIPF2_0Dlic0Ct3beQlHZpFgIOaQ,6738
|
|
30
|
+
pythonosc/test/test_osc_tcp_server.py,sha256=Yhj5EGsYMpSwsxuHIMFyi5GEZIpPKxSA-LstWVJXhJs,13972
|
|
31
|
+
pythonosc/test/test_tcp_client.py,sha256=BxRhzaiSoiY5Iz_cYlO45WWKejpdLT7HFciMpu5zXX4,2605
|
|
32
|
+
pythonosc/test/test_udp_client.py,sha256=qLP5SPem6T40JbAoRlf5_N258pTvLzgu3BAqC6PkzMI,3487
|
|
33
|
+
pythonosc/udp_client.py,sha256=4E9GU8dPV3RilCskedzzx2jVcgK6EGp1UXrGjYV3ZaI,5105
|
|
34
|
+
python_osc-1.10.3.dist-info/WHEEL,sha256=3OmCxFA0ffBIsX4x0o8CdfSvZAOvO3mzEG8U_z5TvV4,80
|
|
35
|
+
python_osc-1.10.3.dist-info/METADATA,sha256=vpKkB59PbIqVCpXGHAI3iN0K05Ql7_TMyw6UPLndAAk,6387
|
|
36
|
+
python_osc-1.10.3.dist-info/RECORD,,
|
pythonosc/dispatcher.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Maps OSC addresses to handler functions"""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import collections
|
|
4
5
|
import inspect
|
|
5
6
|
import logging
|
|
@@ -80,6 +81,46 @@ class Handler(object):
|
|
|
80
81
|
else:
|
|
81
82
|
return self.callback(message.address, *message)
|
|
82
83
|
|
|
84
|
+
async def async_invoke(
|
|
85
|
+
self, client_address: Tuple[str, int], message: OscMessage
|
|
86
|
+
) -> Union[None, AnyStr, Tuple[AnyStr, ArgValue]]:
|
|
87
|
+
"""Invokes the associated callback function (asynchronously)
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
client_address: Address match that causes the invocation
|
|
91
|
+
message: Message causing invocation
|
|
92
|
+
Returns:
|
|
93
|
+
The result of the handler function can be None, a string OSC address, or a tuple of the OSC address
|
|
94
|
+
and arguments.
|
|
95
|
+
"""
|
|
96
|
+
cb = self.callback
|
|
97
|
+
is_async = inspect.iscoroutinefunction(cb)
|
|
98
|
+
|
|
99
|
+
if self.needs_reply_address:
|
|
100
|
+
if self.args:
|
|
101
|
+
if is_async:
|
|
102
|
+
return await cb(
|
|
103
|
+
client_address, message.address, self.args, *message
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
return cb(client_address, message.address, self.args, *message)
|
|
107
|
+
else:
|
|
108
|
+
if is_async:
|
|
109
|
+
return await cb(client_address, message.address, *message)
|
|
110
|
+
else:
|
|
111
|
+
return cb(client_address, message.address, *message)
|
|
112
|
+
else:
|
|
113
|
+
if self.args:
|
|
114
|
+
if is_async:
|
|
115
|
+
return await cb(message.address, self.args, *message)
|
|
116
|
+
else:
|
|
117
|
+
return cb(message.address, self.args, *message)
|
|
118
|
+
else:
|
|
119
|
+
if is_async:
|
|
120
|
+
return await cb(message.address, *message)
|
|
121
|
+
else:
|
|
122
|
+
return cb(message.address, *message)
|
|
123
|
+
|
|
83
124
|
|
|
84
125
|
class Dispatcher(object):
|
|
85
126
|
"""Maps Handlers to OSC addresses and dispatches messages to the handler on matched addresses
|
|
@@ -87,9 +128,20 @@ class Dispatcher(object):
|
|
|
87
128
|
Maps OSC addresses to handler functions and invokes the correct handler when a message comes in.
|
|
88
129
|
"""
|
|
89
130
|
|
|
90
|
-
def __init__(self) -> None:
|
|
131
|
+
def __init__(self, strict_timing: bool = True) -> None:
|
|
132
|
+
"""Initialize the dispatcher.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
strict_timing: Whether to automatically schedule messages with future timetags.
|
|
136
|
+
If True (default), the dispatcher will wait (using sleep) until the specified
|
|
137
|
+
timetag before invoking handlers.
|
|
138
|
+
If False, messages are dispatched immediately regardless of their timetag.
|
|
139
|
+
Disabling this can prevent memory/thread accumulation issues when receiving
|
|
140
|
+
many future-dated messages.
|
|
141
|
+
"""
|
|
91
142
|
self._map: DefaultDict[str, List[Handler]] = collections.defaultdict(list)
|
|
92
143
|
self._default_handler: Optional[Handler] = None
|
|
144
|
+
self._strict_timing = strict_timing
|
|
93
145
|
|
|
94
146
|
def map(
|
|
95
147
|
self,
|
|
@@ -102,13 +154,15 @@ class Dispatcher(object):
|
|
|
102
154
|
|
|
103
155
|
The callback function must have one of the following signatures:
|
|
104
156
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
157
|
+
.. code-block:: python
|
|
158
|
+
|
|
159
|
+
def some_cb(address: str, *osc_args: ArgValue) -> Union[None, str, Tuple[str, ArgValue]]: ...
|
|
160
|
+
def some_cb(address: str, fixed_args: List[Any], *osc_args: ArgValue) -> Union[None, str, Tuple[str, ArgValue]]: ...
|
|
161
|
+
def some_cb(client_address: Tuple[str, int], address: str, *osc_args: ArgValue) -> Union[None, str, Tuple[str, ArgValue]]: ...
|
|
162
|
+
def some_cb(client_address: Tuple[str, int], address: str, fixed_args: List[Any], *osc_args: ArgValue) -> Union[None, str, Tuple[str, ArgValue]]: ...
|
|
108
163
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
``def some_cb(client_address: Tuple[str, int], address: str, fixed_args: List[Any], *osc_args: List[Any]) -> Union[None, AnyStr, Tuple(str, ArgValue)]:``
|
|
164
|
+
Each variadic OSC argument is a single ArgValue, not a list of all arguments.
|
|
165
|
+
Any fixed arguments passed to map() are collected into the separate fixed_args list.
|
|
112
166
|
|
|
113
167
|
The callback function can return None, or a string representing an OSC address to be returned to the client,
|
|
114
168
|
or a tuple that includes the address and ArgValue which will be converted to an OSC message and returned to
|
|
@@ -240,8 +294,8 @@ class Dispatcher(object):
|
|
|
240
294
|
matched = False
|
|
241
295
|
for addr, handlers in self._map.items():
|
|
242
296
|
if patterncompiled.match(addr) or (
|
|
243
|
-
|
|
244
|
-
and re.match(addr.replace("*", "
|
|
297
|
+
"*" in addr
|
|
298
|
+
and re.match(addr.replace("*", ".*?") + "$", address_pattern)
|
|
245
299
|
):
|
|
246
300
|
yield from handlers
|
|
247
301
|
matched = True
|
|
@@ -272,7 +326,7 @@ class Dispatcher(object):
|
|
|
272
326
|
if not handlers:
|
|
273
327
|
continue
|
|
274
328
|
# If the message is to be handled later, then so be it.
|
|
275
|
-
if timed_msg.time > now:
|
|
329
|
+
if self._strict_timing and timed_msg.time > now:
|
|
276
330
|
time.sleep(timed_msg.time - now)
|
|
277
331
|
for handler in handlers:
|
|
278
332
|
result = handler.invoke(client_address, timed_msg.message)
|
|
@@ -309,46 +363,13 @@ class Dispatcher(object):
|
|
|
309
363
|
if not handlers:
|
|
310
364
|
continue
|
|
311
365
|
# If the message is to be handled later, then so be it.
|
|
312
|
-
if timed_msg.time > now:
|
|
313
|
-
|
|
366
|
+
if self._strict_timing and timed_msg.time > now:
|
|
367
|
+
await asyncio.sleep(timed_msg.time - now)
|
|
314
368
|
for handler in handlers:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
timed_msg.message.address,
|
|
320
|
-
handler.args,
|
|
321
|
-
*timed_msg.message,
|
|
322
|
-
)
|
|
323
|
-
elif handler.args:
|
|
324
|
-
result = await handler.callback(
|
|
325
|
-
timed_msg.message.address,
|
|
326
|
-
handler.args,
|
|
327
|
-
*timed_msg.message,
|
|
328
|
-
)
|
|
329
|
-
else:
|
|
330
|
-
result = await handler.callback(
|
|
331
|
-
timed_msg.message.address, *timed_msg.message
|
|
332
|
-
)
|
|
333
|
-
else:
|
|
334
|
-
if handler.needs_reply_address:
|
|
335
|
-
result = handler.callback(
|
|
336
|
-
client_address,
|
|
337
|
-
timed_msg.message.address,
|
|
338
|
-
handler.args,
|
|
339
|
-
*timed_msg.message,
|
|
340
|
-
)
|
|
341
|
-
elif handler.args:
|
|
342
|
-
result = handler.callback(
|
|
343
|
-
timed_msg.message.address,
|
|
344
|
-
handler.args,
|
|
345
|
-
*timed_msg.message,
|
|
346
|
-
)
|
|
347
|
-
else:
|
|
348
|
-
result = handler.callback(
|
|
349
|
-
timed_msg.message.address, *timed_msg.message
|
|
350
|
-
)
|
|
351
|
-
if result:
|
|
369
|
+
result = await handler.async_invoke(
|
|
370
|
+
client_address, timed_msg.message
|
|
371
|
+
)
|
|
372
|
+
if result is not None:
|
|
352
373
|
results.append(result)
|
|
353
374
|
except osc_packet.ParseError:
|
|
354
375
|
pass
|
pythonosc/osc_bundle.py
CHANGED
|
@@ -50,6 +50,12 @@ class OscBundle(object):
|
|
|
50
50
|
while self._dgram[index:]:
|
|
51
51
|
# Get the sub content size.
|
|
52
52
|
content_size, index = osc_types.get_int(self._dgram, index)
|
|
53
|
+
if content_size <= 0 or content_size % 4:
|
|
54
|
+
raise ParseError(
|
|
55
|
+
"Invalid bundle element size: {}".format(content_size)
|
|
56
|
+
)
|
|
57
|
+
if content_size > len(self._dgram) - index:
|
|
58
|
+
raise ParseError("Bundle element exceeds the datagram length")
|
|
53
59
|
# Get the datagram for the sub content.
|
|
54
60
|
content_dgram = self._dgram[index : index + content_size]
|
|
55
61
|
# Increment our position index up to the next possible content.
|
pythonosc/osc_message_builder.py
CHANGED
|
@@ -5,7 +5,11 @@ from typing import Any, Iterable, List, Optional, Tuple, Union
|
|
|
5
5
|
from pythonosc import osc_message
|
|
6
6
|
from pythonosc.parsing import osc_types
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# Represents a single OSC argument value.
|
|
9
|
+
# Can be a primitive type, a MIDI packet, or a list/tuple for nested OSC arrays.
|
|
10
|
+
ArgValue = Union[
|
|
11
|
+
str, bytes, bool, int, float, osc_types.MidiPacket, List[Any], Tuple[Any, ...], None
|
|
12
|
+
]
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class BuildError(Exception):
|
|
@@ -68,9 +72,9 @@ class OscMessageBuilder(object):
|
|
|
68
72
|
"""Returns the (type, value) arguments list of this message."""
|
|
69
73
|
return self._args
|
|
70
74
|
|
|
71
|
-
def _valid_type(self, arg_type: str) -> bool:
|
|
72
|
-
if arg_type
|
|
73
|
-
return
|
|
75
|
+
def _valid_type(self, arg_type: Union[str, List[Any]]) -> bool:
|
|
76
|
+
if isinstance(arg_type, str):
|
|
77
|
+
return arg_type in self._SUPPORTED_ARG_TYPES
|
|
74
78
|
elif isinstance(arg_type, list):
|
|
75
79
|
for sub_type in arg_type:
|
|
76
80
|
if not self._valid_type(sub_type):
|
|
@@ -78,7 +82,9 @@ class OscMessageBuilder(object):
|
|
|
78
82
|
return True
|
|
79
83
|
return False
|
|
80
84
|
|
|
81
|
-
def add_arg(
|
|
85
|
+
def add_arg(
|
|
86
|
+
self, arg_value: ArgValue, arg_type: Optional[Union[str, List[Any]]] = None
|
|
87
|
+
) -> None:
|
|
82
88
|
"""Add a typed argument to this message.
|
|
83
89
|
|
|
84
90
|
Args:
|
|
@@ -193,12 +199,17 @@ class OscMessageBuilder(object):
|
|
|
193
199
|
raise BuildError(f"Could not build the message: {be}")
|
|
194
200
|
|
|
195
201
|
|
|
196
|
-
def build_msg(
|
|
202
|
+
def build_msg(
|
|
203
|
+
address: str, value: Union[ArgValue, Iterable[ArgValue]] = ()
|
|
204
|
+
) -> osc_message.OscMessage:
|
|
205
|
+
"""Build a message from a scalar value or an iterable of arguments.
|
|
206
|
+
|
|
207
|
+
Omit the value or pass an empty iterable for a message with no arguments.
|
|
208
|
+
An explicitly supplied empty string is retained as a string argument.
|
|
209
|
+
"""
|
|
197
210
|
builder = OscMessageBuilder(address=address)
|
|
198
211
|
values: Iterable[Any]
|
|
199
|
-
if value
|
|
200
|
-
values = []
|
|
201
|
-
elif not isinstance(value, Iterable) or isinstance(value, (str, bytes)):
|
|
212
|
+
if not isinstance(value, Iterable) or isinstance(value, (str, bytes)):
|
|
202
213
|
values = [value]
|
|
203
214
|
else:
|
|
204
215
|
values = value
|
pythonosc/osc_server.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import os
|
|
5
|
+
import socket
|
|
5
6
|
import socketserver
|
|
6
7
|
from socket import socket as _socket
|
|
7
8
|
from typing import Any, Coroutine, Tuple, Union, cast
|
|
@@ -58,11 +59,16 @@ def _is_valid_request(request: _RequestType) -> bool:
|
|
|
58
59
|
class OSCUDPServer(socketserver.UDPServer):
|
|
59
60
|
"""Superclass for different flavors of OSC UDP servers"""
|
|
60
61
|
|
|
62
|
+
# Receive complete UDP datagrams instead of socketserver's 8192-byte default.
|
|
63
|
+
max_packet_size = 65535
|
|
64
|
+
|
|
61
65
|
def __init__(
|
|
62
66
|
self,
|
|
63
67
|
server_address: Tuple[str, int],
|
|
64
68
|
dispatcher: Dispatcher,
|
|
65
69
|
bind_and_activate: bool = True,
|
|
70
|
+
timeout: float | None = None,
|
|
71
|
+
family: socket.AddressFamily | None = None,
|
|
66
72
|
) -> None:
|
|
67
73
|
"""Initialize
|
|
68
74
|
|
|
@@ -70,9 +76,29 @@ class OSCUDPServer(socketserver.UDPServer):
|
|
|
70
76
|
server_address: IP and port of server
|
|
71
77
|
dispatcher: Dispatcher this server will use
|
|
72
78
|
(optional) bind_and_activate: default=True defines if the server has to start on call of constructor
|
|
79
|
+
(optional) timeout: Default timeout in seconds for socket operations
|
|
80
|
+
(optional) family: socket.AF_INET or socket.AF_INET6. If None, it will be inferred from server_address.
|
|
73
81
|
"""
|
|
82
|
+
if family is not None:
|
|
83
|
+
self.address_family = family
|
|
84
|
+
else:
|
|
85
|
+
# Try to infer address family from server_address
|
|
86
|
+
try:
|
|
87
|
+
infos = socket.getaddrinfo(
|
|
88
|
+
server_address[0],
|
|
89
|
+
server_address[1],
|
|
90
|
+
type=socket.SOCK_DGRAM,
|
|
91
|
+
family=socket.AF_UNSPEC,
|
|
92
|
+
)
|
|
93
|
+
if infos:
|
|
94
|
+
self.address_family = infos[0][0]
|
|
95
|
+
except (socket.gaierror, IndexError):
|
|
96
|
+
# Fallback to default if resolution fails
|
|
97
|
+
pass
|
|
98
|
+
|
|
74
99
|
super().__init__(server_address, _UDPHandler, bind_and_activate)
|
|
75
100
|
self._dispatcher = dispatcher
|
|
101
|
+
self.timeout = timeout
|
|
76
102
|
|
|
77
103
|
def verify_request(
|
|
78
104
|
self, request: _RequestType, client_address: _AddressType
|
pythonosc/osc_tcp_server.py
CHANGED
|
@@ -33,67 +33,37 @@ loop.run_forever()
|
|
|
33
33
|
# mypy: disable-error-code="attr-defined"
|
|
34
34
|
|
|
35
35
|
import asyncio
|
|
36
|
-
import logging
|
|
37
36
|
import os
|
|
37
|
+
import socket
|
|
38
38
|
import socketserver
|
|
39
|
-
import
|
|
40
|
-
from typing import List, Tuple
|
|
39
|
+
from typing import Tuple
|
|
41
40
|
|
|
42
|
-
from pythonosc import osc_message_builder
|
|
41
|
+
from pythonosc import osc_message_builder
|
|
43
42
|
from pythonosc.dispatcher import Dispatcher
|
|
43
|
+
from pythonosc.parsing.framing import (
|
|
44
|
+
BINARY_LENGTH_FRAMING,
|
|
45
|
+
SLIP_FRAMING,
|
|
46
|
+
Framing,
|
|
47
|
+
FramingParser,
|
|
48
|
+
)
|
|
44
49
|
|
|
45
|
-
LOG = logging.getLogger()
|
|
46
50
|
MODE_1_0 = "1.0"
|
|
47
51
|
MODE_1_1 = "1.1"
|
|
52
|
+
FRAMING_BY_MODE: dict[str, Framing] = {
|
|
53
|
+
MODE_1_0: BINARY_LENGTH_FRAMING,
|
|
54
|
+
MODE_1_1: SLIP_FRAMING,
|
|
55
|
+
}
|
|
48
56
|
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
def get_framing(mode: str) -> Framing:
|
|
59
|
+
try:
|
|
60
|
+
return FRAMING_BY_MODE[mode]
|
|
61
|
+
except KeyError:
|
|
62
|
+
raise ValueError(f"Unsupported OSC mode: {mode}") from None
|
|
52
63
|
|
|
53
|
-
Whether this will be run on its own thread, the server's or a whole new
|
|
54
|
-
process depends on the server you instantiated, look at their documentation.
|
|
55
|
-
|
|
56
|
-
This method is called after a basic sanity check was done on the datagram,
|
|
57
|
-
basically whether this datagram looks like an osc message or bundle,
|
|
58
|
-
if not the server won't even bother to call it and so no new
|
|
59
|
-
threads/processes will be spawned.
|
|
60
|
-
"""
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
while True:
|
|
65
|
-
lengthbuf = self.recvall(4)
|
|
66
|
-
if lengthbuf == b"":
|
|
67
|
-
break
|
|
68
|
-
(length,) = struct.unpack("!I", lengthbuf)
|
|
69
|
-
data = self.recvall(length)
|
|
70
|
-
if data == b"":
|
|
71
|
-
break
|
|
72
|
-
|
|
73
|
-
resp = self.server.dispatcher.call_handlers_for_packet(
|
|
74
|
-
data, self.client_address
|
|
75
|
-
)
|
|
76
|
-
# resp = _call_handlers_for_packet(data, self.server.dispatcher)
|
|
77
|
-
for r in resp:
|
|
78
|
-
if not isinstance(r, tuple):
|
|
79
|
-
r = [r]
|
|
80
|
-
msg = osc_message_builder.build_msg(r[0], r[1:])
|
|
81
|
-
b = struct.pack("!I", len(msg.dgram))
|
|
82
|
-
self.request.sendall(b + msg.dgram)
|
|
83
|
-
|
|
84
|
-
def recvall(self, count: int) -> bytes:
|
|
85
|
-
buf = b""
|
|
86
|
-
while count > 0:
|
|
87
|
-
newbuf = self.request.recv(count)
|
|
88
|
-
if not newbuf:
|
|
89
|
-
return b""
|
|
90
|
-
buf += newbuf
|
|
91
|
-
count -= len(newbuf)
|
|
92
|
-
return buf
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class _TCPHandler1_1(socketserver.BaseRequestHandler):
|
|
96
|
-
"""Handles correct OSC1.1 messages.
|
|
65
|
+
class TCPHandler(socketserver.BaseRequestHandler):
|
|
66
|
+
"""Handles correct OSC messages.
|
|
97
67
|
|
|
98
68
|
Whether this will be run on its own thread, the server's or a whole new
|
|
99
69
|
process depends on the server you instantiated, look at their documentation.
|
|
@@ -105,14 +75,12 @@ class _TCPHandler1_1(socketserver.BaseRequestHandler):
|
|
|
105
75
|
"""
|
|
106
76
|
|
|
107
77
|
def handle(self) -> None:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
for p in packets:
|
|
115
|
-
# resp = _call_handlers_for_packet(p, self.server.dispatcher)
|
|
78
|
+
assert isinstance(self.server, OSCTCPServer)
|
|
79
|
+
|
|
80
|
+
parser = FramingParser(self.server.framing)
|
|
81
|
+
|
|
82
|
+
while chunk := self.request.recv(16384):
|
|
83
|
+
for p in parser.feed(chunk):
|
|
116
84
|
resp = self.server.dispatcher.call_handlers_for_packet(
|
|
117
85
|
p, self.client_address
|
|
118
86
|
)
|
|
@@ -120,47 +88,44 @@ class _TCPHandler1_1(socketserver.BaseRequestHandler):
|
|
|
120
88
|
if not isinstance(r, tuple):
|
|
121
89
|
r = [r]
|
|
122
90
|
msg = osc_message_builder.build_msg(r[0], r[1:])
|
|
123
|
-
self.request.sendall(
|
|
124
|
-
|
|
125
|
-
def recvall(self) -> List[bytes]:
|
|
126
|
-
buf = self.request.recv(4096)
|
|
127
|
-
if not buf:
|
|
128
|
-
return []
|
|
129
|
-
# If the last byte is not an END marker there could be more data coming
|
|
130
|
-
while buf[-1] != 192:
|
|
131
|
-
newbuf = self.request.recv(4096)
|
|
132
|
-
if not newbuf:
|
|
133
|
-
# Maybe should raise an exception here?
|
|
134
|
-
break
|
|
135
|
-
buf += newbuf
|
|
136
|
-
|
|
137
|
-
packets = [slip.decode(p) for p in buf.split(slip.END_END)]
|
|
138
|
-
return packets
|
|
91
|
+
self.request.sendall(self.server.framing.encode(msg.dgram))
|
|
139
92
|
|
|
140
93
|
|
|
141
94
|
class OSCTCPServer(socketserver.TCPServer):
|
|
142
95
|
"""Superclass for different flavors of OSCTCPServer"""
|
|
143
96
|
|
|
97
|
+
dispatcher: Dispatcher
|
|
98
|
+
framing: Framing
|
|
99
|
+
|
|
144
100
|
def __init__(
|
|
145
101
|
self,
|
|
146
102
|
server_address: Tuple[str | bytes | bytearray, int],
|
|
147
103
|
dispatcher: Dispatcher,
|
|
148
104
|
mode: str = MODE_1_1,
|
|
105
|
+
family: socket.AddressFamily | None = None,
|
|
149
106
|
):
|
|
150
107
|
self.request_queue_size = 300
|
|
151
|
-
self.
|
|
152
|
-
|
|
153
|
-
raise ValueError("OSC Mode must be '1.0' or '1.1'")
|
|
154
|
-
if self.mode == MODE_1_0:
|
|
155
|
-
super().__init__(server_address, _TCPHandler1_0)
|
|
156
|
-
else:
|
|
157
|
-
super().__init__(server_address, _TCPHandler1_1)
|
|
158
|
-
self._dispatcher = dispatcher
|
|
108
|
+
self.dispatcher = dispatcher
|
|
109
|
+
self.framing = get_framing(mode)
|
|
159
110
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
111
|
+
if family is not None:
|
|
112
|
+
self.address_family = family
|
|
113
|
+
elif isinstance(server_address[0], str):
|
|
114
|
+
# Try to infer address family from server_address
|
|
115
|
+
try:
|
|
116
|
+
infos = socket.getaddrinfo(
|
|
117
|
+
server_address[0],
|
|
118
|
+
server_address[1],
|
|
119
|
+
type=socket.SOCK_STREAM,
|
|
120
|
+
family=socket.AF_UNSPEC,
|
|
121
|
+
)
|
|
122
|
+
if infos:
|
|
123
|
+
self.address_family = infos[0][0]
|
|
124
|
+
except (socket.gaierror, IndexError):
|
|
125
|
+
# Fallback to default if resolution fails
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
super().__init__(server_address, TCPHandler)
|
|
164
129
|
|
|
165
130
|
|
|
166
131
|
class BlockingOSCTCPServer(OSCTCPServer):
|
|
@@ -210,8 +175,8 @@ class AsyncOSCTCPServer:
|
|
|
210
175
|
"""
|
|
211
176
|
self._port = port
|
|
212
177
|
self._server_address = server_address
|
|
213
|
-
self.
|
|
214
|
-
self.
|
|
178
|
+
self.dispatcher = dispatcher
|
|
179
|
+
self.framing = get_framing(mode)
|
|
215
180
|
|
|
216
181
|
async def __aenter__(self):
|
|
217
182
|
return self
|
|
@@ -225,9 +190,6 @@ class AsyncOSCTCPServer:
|
|
|
225
190
|
self.handle, self._server_address, self._port
|
|
226
191
|
)
|
|
227
192
|
|
|
228
|
-
addrs = ", ".join(str(sock.getsockname()) for sock in self._server.sockets)
|
|
229
|
-
LOG.debug(f"Serving on {addrs}")
|
|
230
|
-
|
|
231
193
|
async with self._server:
|
|
232
194
|
await self._server.serve_forever()
|
|
233
195
|
|
|
@@ -235,86 +197,28 @@ class AsyncOSCTCPServer:
|
|
|
235
197
|
self._server.close()
|
|
236
198
|
await self._server.wait_closed()
|
|
237
199
|
|
|
238
|
-
@property
|
|
239
|
-
def dispatcher(self):
|
|
240
|
-
return self._dispatcher
|
|
241
|
-
|
|
242
200
|
async def handle(
|
|
243
201
|
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
|
244
202
|
) -> None:
|
|
203
|
+
try:
|
|
204
|
+
await self._handle(reader, writer)
|
|
205
|
+
finally:
|
|
206
|
+
if writer.can_write_eof():
|
|
207
|
+
writer.write_eof()
|
|
208
|
+
writer.close()
|
|
209
|
+
await writer.wait_closed()
|
|
210
|
+
|
|
211
|
+
async def _handle(
|
|
212
|
+
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
|
213
|
+
) -> None:
|
|
214
|
+
parser = FramingParser(self.framing)
|
|
245
215
|
client_address = ("", 0)
|
|
246
216
|
sock = writer.transport.get_extra_info("socket")
|
|
247
217
|
if sock is not None:
|
|
248
218
|
client_address = sock.getpeername()
|
|
249
219
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
else:
|
|
253
|
-
await self.handle1_0(reader, writer, client_address)
|
|
254
|
-
writer.write_eof()
|
|
255
|
-
LOG.debug("Close the connection")
|
|
256
|
-
writer.close()
|
|
257
|
-
await writer.wait_closed()
|
|
258
|
-
|
|
259
|
-
async def handle1_0(
|
|
260
|
-
self,
|
|
261
|
-
reader: asyncio.StreamReader,
|
|
262
|
-
writer: asyncio.StreamWriter,
|
|
263
|
-
client_address: Tuple[str, int],
|
|
264
|
-
) -> None:
|
|
265
|
-
LOG.debug("Incoming socket open 1.0")
|
|
266
|
-
while True:
|
|
267
|
-
try:
|
|
268
|
-
buf = await reader.read(4)
|
|
269
|
-
except Exception as e:
|
|
270
|
-
LOG.exception("Read error", e)
|
|
271
|
-
return
|
|
272
|
-
if buf == b"":
|
|
273
|
-
break
|
|
274
|
-
(length,) = struct.unpack("!I", buf)
|
|
275
|
-
buf = b""
|
|
276
|
-
while length > 0:
|
|
277
|
-
newbuf = await reader.read(length)
|
|
278
|
-
if not newbuf:
|
|
279
|
-
break
|
|
280
|
-
buf += newbuf
|
|
281
|
-
length -= len(newbuf)
|
|
282
|
-
|
|
283
|
-
result = await self.dispatcher.async_call_handlers_for_packet(
|
|
284
|
-
buf, client_address
|
|
285
|
-
)
|
|
286
|
-
for r in result:
|
|
287
|
-
if not isinstance(r, tuple):
|
|
288
|
-
r = [r]
|
|
289
|
-
msg = osc_message_builder.build_msg(r[0], r[1:])
|
|
290
|
-
b = struct.pack("!I", len(msg.dgram))
|
|
291
|
-
writer.write(b + msg.dgram)
|
|
292
|
-
await writer.drain()
|
|
293
|
-
|
|
294
|
-
async def handle_1_1(
|
|
295
|
-
self,
|
|
296
|
-
reader: asyncio.StreamReader,
|
|
297
|
-
writer: asyncio.StreamWriter,
|
|
298
|
-
client_address: Tuple[str, int],
|
|
299
|
-
) -> None:
|
|
300
|
-
LOG.debug("Incoming socket open 1.1")
|
|
301
|
-
while True:
|
|
302
|
-
try:
|
|
303
|
-
buf = await reader.read(4096)
|
|
304
|
-
except Exception as e:
|
|
305
|
-
LOG.exception("Read error", e)
|
|
306
|
-
return
|
|
307
|
-
if buf == b"":
|
|
308
|
-
break
|
|
309
|
-
while len(buf) > 0 and buf[-1] != 192:
|
|
310
|
-
newbuf = await reader.read(4096)
|
|
311
|
-
if not newbuf:
|
|
312
|
-
# Maybe should raise an exception here?
|
|
313
|
-
break
|
|
314
|
-
buf += newbuf
|
|
315
|
-
|
|
316
|
-
packets = [slip.decode(p) for p in buf.split(slip.END_END)]
|
|
317
|
-
for p in packets:
|
|
220
|
+
while chunk := await reader.read(16384):
|
|
221
|
+
for p in parser.feed(chunk):
|
|
318
222
|
result = await self.dispatcher.async_call_handlers_for_packet(
|
|
319
223
|
p, client_address
|
|
320
224
|
)
|
|
@@ -322,5 +226,5 @@ class AsyncOSCTCPServer:
|
|
|
322
226
|
if not isinstance(r, tuple):
|
|
323
227
|
r = [r]
|
|
324
228
|
msg = osc_message_builder.build_msg(r[0], r[1:])
|
|
325
|
-
writer.write(
|
|
229
|
+
writer.write(self.framing.encode(msg.dgram))
|
|
326
230
|
await writer.drain()
|