diff --git a/pyproject.toml b/pyproject.toml index 645c098..c1cefc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "vban-cmd" -version = "1.3.2" +version = "1.3.3" description = "Python interface for the VBAN RT Packet Service (Sendtext)" authors = ["onyx-and-iris "] license = "MIT" diff --git a/vban_cmd/base.py b/vban_cmd/base.py index a9ff3b5..bb32707 100644 --- a/vban_cmd/base.py +++ b/vban_cmd/base.py @@ -1,17 +1,17 @@ import socket import time from abc import ABCMeta, abstractmethod -from typing import Iterable, NoReturn, Optional, Union +from typing import Iterable, Optional, Union from .misc import Event -from .packet import TextRequestHeader +from .packet import RequestHeader from .subject import Subject from .util import Socket, comp, script from .worker import Subscriber, Updater class VbanCmd(metaclass=ABCMeta): - """Base class responsible for communicating over VBAN RT Service""" + """Base class responsible for communicating with the VBAN RT Packet Service""" DELAY = 0.001 # fmt: off @@ -26,7 +26,7 @@ class VbanCmd(metaclass=ABCMeta): for attr, val in kwargs.items(): setattr(self, attr, val) - self.text_header = TextRequestHeader( + self.packet_request = RequestHeader( name=self.streamname, bps_index=self.BPS_OPTS.index(self.bps), channel=self.channel, @@ -66,11 +66,11 @@ class VbanCmd(metaclass=ABCMeta): """Sends a string request command over a network.""" cmd = id_ if not param else f"{id_}.{param}={val}" self.socks[Socket.request].sendto( - self.text_header.header + cmd.encode(), + self.packet_request.header + cmd.encode(), (socket.gethostbyname(self.ip), self.port), ) - count = int.from_bytes(self.text_header.framecounter, "little") + 1 - self.text_header.framecounter = count.to_bytes(4, "little") + count = int.from_bytes(self.packet_request.framecounter, "little") + 1 + self.packet_request.framecounter = count.to_bytes(4, "little") if param: self.cache[f"{id_}.{param}"] = val if self.sync: diff --git a/vban_cmd/packet.py b/vban_cmd/packet.py index 96ef829..8297177 100644 --- a/vban_cmd/packet.py +++ b/vban_cmd/packet.py @@ -8,31 +8,32 @@ HEADER_SIZE = 4 + 1 + 1 + 1 + 1 + 16 + 4 @dataclass -class VBAN_VMRT_Packet_Data: - """Represents the structure of a VMRT data packet""" +class VbanRtPacket: + """Represents the body of a VBAN RT data packet""" - _voicemeeterType: bytes - _reserved: bytes - _buffersize: bytes - _voicemeeterVersion: bytes - _optionBits: bytes - _samplerate: bytes - _inputLeveldB100: bytes - _outputLeveldB100: bytes - _TransportBit: bytes - _stripState: bytes - _busState: bytes - _stripGaindB100Layer1: bytes - _stripGaindB100Layer2: bytes - _stripGaindB100Layer3: bytes - _stripGaindB100Layer4: bytes - _stripGaindB100Layer5: bytes - _stripGaindB100Layer6: bytes - _stripGaindB100Layer7: bytes - _stripGaindB100Layer8: bytes - _busGaindB100: bytes - _stripLabelUTF8c60: bytes - _busLabelUTF8c60: bytes + def __init__(self, data): + self._voicemeeterType: bytes = data[28:29] + self._reserved: bytes = data[29:30] + self._buffersize: bytes = data[30:32] + self._voicemeeterVersion: bytes = data[32:36] + self._optionBits: bytes = data[36:40] + self._samplerate: bytes = data[40:44] + self._inputLeveldB100: bytes = data[44:112] + self._outputLeveldB100: bytes = data[112:240] + self._TransportBit: bytes = data[240:244] + self._stripState: bytes = data[244:276] + self._busState: bytes = data[276:308] + self._stripGaindB100Layer1: bytes = data[308:324] + self._stripGaindB100Layer2: bytes = data[324:340] + self._stripGaindB100Layer3: bytes = data[340:356] + self._stripGaindB100Layer4: bytes = data[356:372] + self._stripGaindB100Layer5: bytes = data[372:388] + self._stripGaindB100Layer6: bytes = data[388:404] + self._stripGaindB100Layer7: bytes = data[404:420] + self._stripGaindB100Layer8: bytes = data[420:436] + self._busGaindB100: bytes = data[436:452] + self._stripLabelUTF8c60: bytes = data[452:932] + self._busLabelUTF8c60: bytes = data[932:1412] def pdirty(self, other): """True iff any defined parameter has changed""" @@ -201,8 +202,8 @@ class VBAN_VMRT_Packet_Data: @dataclass -class VBAN_VMRT_Packet_Header: - """Represents a RESPONSE RT PACKET header""" +class VbanRtPacketHeader: + """Represents the header of VBAN RT data packet""" name = "Voicemeeter-RTP" vban: bytes = "VBAN".encode() @@ -225,7 +226,7 @@ class VBAN_VMRT_Packet_Header: @dataclass -class TextRequestHeader: +class RequestHeader: """Represents a REQUEST RT PACKET header""" name: str @@ -262,8 +263,8 @@ class TextRequestHeader: @dataclass -class RegisterRTHeader: - """Represents a REGISTER RT PACKET header""" +class SubscribeHeader: + """Represents a packet used to subscribe to the RT Packet Service""" name = "Register RTP" timeout = 15 diff --git a/vban_cmd/worker.py b/vban_cmd/worker.py index 0115976..348c949 100644 --- a/vban_cmd/worker.py +++ b/vban_cmd/worker.py @@ -1,15 +1,8 @@ import socket import threading import time -from enum import IntEnum -from typing import Optional -from .packet import ( - HEADER_SIZE, - RegisterRTHeader, - VBAN_VMRT_Packet_Data, - VBAN_VMRT_Packet_Header, -) +from .packet import HEADER_SIZE, SubscribeHeader, VbanRtPacket, VbanRtPacketHeader from .util import Socket @@ -17,23 +10,23 @@ class Subscriber(threading.Thread): """fire a subscription packet every 10 seconds""" def __init__(self, remote): - super().__init__(name="subscriber", target=self.register, daemon=True) - self._rem = remote - self.register_header = RegisterRTHeader() + super().__init__(name="subscriber", target=self.subscribe, daemon=True) + self._remote = remote + self.packet = SubscribeHeader() - def register(self): - while self._rem.running: + def subscribe(self): + while self._remote.running: try: - self._rem.socks[Socket.register].sendto( - self.register_header.header, - (socket.gethostbyname(self._rem.ip), self._rem.port), + self._remote.socks[Socket.register].sendto( + self.packet.header, + (socket.gethostbyname(self._remote.ip), self._remote.port), ) - count = int.from_bytes(self.register_header.framecounter, "little") + 1 - self.register_header.framecounter = count.to_bytes(4, "little") + count = int.from_bytes(self.packet.framecounter, "little") + 1 + self.packet.framecounter = count.to_bytes(4, "little") time.sleep(10) except socket.gaierror as e: - print(f"Unable to resolve hostname {self._rem.ip}") - self._rem.socks[Socket.register].close() + print(f"Unable to resolve hostname {self._remote.ip}") + self._remote.socks[Socket.register].close() raise e @@ -46,78 +39,48 @@ class Updater(threading.Thread): def __init__(self, remote): super().__init__(name="updater", target=self.update, daemon=True) - self._rem = remote - self._rem.socks[Socket.response].bind( - (socket.gethostbyname(socket.gethostname()), self._rem.port) + self._remote = remote + self._remote.socks[Socket.response].bind( + (socket.gethostbyname(socket.gethostname()), self._remote.port) ) - self.expected_packet = VBAN_VMRT_Packet_Header() - self._rem._public_packet = self._get_rt() + self.packet_expected = VbanRtPacketHeader() + self._remote._public_packet = self._get_rt() - def _fetch_rt_packet(self) -> Optional[VBAN_VMRT_Packet_Data]: - """Returns a valid RT Data Packet or None""" - data, _ = self._rem.socks[Socket.response].recvfrom(2048) - # check for packet data - if len(data) > HEADER_SIZE: - # check if packet is of type VBAN - if self.expected_packet.header == data[: HEADER_SIZE - 4]: - return VBAN_VMRT_Packet_Data( - _voicemeeterType=data[28:29], - _reserved=data[29:30], - _buffersize=data[30:32], - _voicemeeterVersion=data[32:36], - _optionBits=data[36:40], - _samplerate=data[40:44], - _inputLeveldB100=data[44:112], - _outputLeveldB100=data[112:240], - _TransportBit=data[240:244], - _stripState=data[244:276], - _busState=data[276:308], - _stripGaindB100Layer1=data[308:324], - _stripGaindB100Layer2=data[324:340], - _stripGaindB100Layer3=data[340:356], - _stripGaindB100Layer4=data[356:372], - _stripGaindB100Layer5=data[372:388], - _stripGaindB100Layer6=data[388:404], - _stripGaindB100Layer7=data[404:420], - _stripGaindB100Layer8=data[420:436], - _busGaindB100=data[436:452], - _stripLabelUTF8c60=data[452:932], - _busLabelUTF8c60=data[932:1412], - ) - - def _get_rt(self) -> VBAN_VMRT_Packet_Data: + def _get_rt(self) -> VbanRtPacket: """Attempt to fetch data packet until a valid one found""" - def fget(): - data = False - while not data: - data = self._fetch_rt_packet() - time.sleep(self._rem.DELAY) - return data - - return fget() + while True: + data, _ = self._remote.socks[Socket.response].recvfrom(2048) + # check for packet data + if len(data) > HEADER_SIZE: + # check if packet is of type rt packet response + if self.packet_expected.header == data[: HEADER_SIZE - 4]: + return VbanRtPacket(data) + time.sleep(self._remote.DELAY) def update(self): - print(f"Listening for {', '.join(self._rem.event.get())} events") + print(f"Listening for {', '.join(self._remote.event.get())} events") ( - self._rem.cache["strip_level"], - self._rem.cache["bus_level"], - ) = self._rem._get_levels(self._rem.public_packet) + self._remote.cache["strip_level"], + self._remote.cache["bus_level"], + ) = self._remote._get_levels(self._remote.public_packet) - while self._rem.running: + while self._remote.running: start = time.time() _pp = self._get_rt() - self._rem._strip_buf, self._rem._bus_buf = self._rem._get_levels(_pp) - self._rem._pdirty = _pp.pdirty(self._rem.public_packet) + self._remote._strip_buf, self._remote._bus_buf = self._remote._get_levels( + _pp + ) + self._remote._pdirty = _pp.pdirty(self._remote.public_packet) - if self._rem.event.ldirty and self._rem.ldirty: - self._rem.cache["strip_level"] = self._rem._strip_buf - self._rem.cache["bus_level"] = self._rem._bus_buf - self._rem.subject.notify("ldirty") - if self._rem.public_packet != _pp: - self._rem._public_packet = _pp - if self._rem.event.pdirty and self._rem.pdirty: - self._rem.subject.notify("pdirty") + if self._remote.event.ldirty and self._remote.ldirty: + self._remote.cache["strip_level"] = self._remote._strip_buf + self._remote.cache["bus_level"] = self._remote._bus_buf + self._remote.subject.notify("ldirty") + if self._remote.public_packet != _pp: + self._remote._public_packet = _pp + if self._remote.event.pdirty and self._remote.pdirty: + self._remote.subject.notify("pdirty") elapsed = time.time() - start - if self._rem.ratelimit - elapsed > 0: - time.sleep(self._rem.ratelimit - elapsed) + if self._remote.ratelimit - elapsed > 0: + time.sleep(self._remote.ratelimit - elapsed)