diff --git a/.github/workflows/publish_stable.yml b/.github/workflows/publish_stable.yml index 58871a0..e8f562f 100644 --- a/.github/workflows/publish_stable.yml +++ b/.github/workflows/publish_stable.yml @@ -10,7 +10,7 @@ jobs: secrets: inherit with: branch: 'master' - version_file: 'hivemind_listener/version.py' + version_file: 'hivemind_audio_binary_protocol/version.py' setup_py: 'setup.py' publish_release: true diff --git a/.github/workflows/release_workflow.yml b/.github/workflows/release_workflow.yml index 3a0f456..10e123e 100644 --- a/.github/workflows/release_workflow.yml +++ b/.github/workflows/release_workflow.yml @@ -12,7 +12,7 @@ jobs: secrets: inherit with: branch: 'dev' - version_file: 'hivemind_listener/version.py' + version_file: 'hivemind_audio_binary_protocol/version.py' setup_py: 'setup.py' update_changelog: true publish_prerelease: true diff --git a/README.md b/README.md index 976160a..67e618a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# HiveMind Listener +# HiveMind Audio Binary Protocol Plugin -HiveMind Listener extends [hivemind-core](https://github.com/JarbasHiveMind/hivemind-core) and integrates with [ovos-simple-listener](https://github.com/TigreGotico/ovos-simple-listener), enabling audio-based communication with advanced features for **secure, distributed voice assistant functionality**. +Extends [hivemind-core](https://github.com/JarbasHiveMind/hivemind-core) and integrates with [ovos-simple-listener](https://github.com/TigreGotico/ovos-simple-listener), enabling audio-based communication with advanced features for **secure, distributed voice assistant functionality**. --- @@ -20,7 +20,7 @@ HiveMind Listener extends [hivemind-core](https://github.com/JarbasHiveMind/hive Running **TTS/STT via HiveMind Listener** requires an access key, offering fine-grained **access control** compared to non-authenticated server plugins. -> 💡 **Tip**: HiveMind Listener replaces `hivemind-core` and is compatible with all existing HiveMind clients. +> 💡 **Tip**: `hivemind-audio-binary-protocol` is a plugin for `hivemind-core` and is compatible with all existing HiveMind clients. --- @@ -29,54 +29,7 @@ HiveMind Listener extends [hivemind-core](https://github.com/JarbasHiveMind/hive ### Installation ```bash -pip install hivemind-listener -``` - ---- - -## 🛠️ Commands Overview - -```bash -$ hivemind-listener --help -Usage: hivemind-listener [OPTIONS] - - Run the HiveMind Listener with configurable plugins. - - If a plugin is not specified, the defaults from mycroft.conf will be used. - - mycroft.conf will be loaded as usual for plugin settings - -Options: - --wakeword TEXT Specify the wake word for the listener. - Default is 'hey_mycroft'. - --stt-plugin TEXT Specify the STT plugin to use. - --tts-plugin TEXT Specify the TTS plugin to use. - --vad-plugin TEXT Specify the VAD plugin to use. - --dialog-transformers TEXT dialog transformer plugins to load. - Installed plugins: None - --utterance-transformers TEXT utterance transformer plugins to load. - Installed plugins: ['ovos-utterance-plugin-cancel'] - --metadata-transformers TEXT metadata transformer plugins to - load. Installed plugins: None - --ovos_bus_address TEXT Open Voice OS bus address - --ovos_bus_port INTEGER Open Voice OS bus port number - --host TEXT HiveMind host - --port INTEGER HiveMind port number - --ssl BOOLEAN use wss:// - --cert_dir TEXT HiveMind SSL certificate directory - --cert_name TEXT HiveMind SSL certificate file name - --db-backend [redis|json|sqlite] - Select the database backend to use. Options: - redis, sqlite, json. - --db-name TEXT [json/sqlite] The name for the database - file. ~/.cache/hivemind-core/{name} - --db-folder TEXT [json/sqlite] The subfolder where database - files are stored. ~/.cache/{db_folder}} - --redis-host TEXT [redis] Host for Redis. Default is - localhost. - --redis-port INTEGER [redis] Port for Redis. Default is 6379. - --redis-password TEXT [redis] Password for Redis. Default None - --help Show this message and exit. +pip install hivemind-audio-binary-protocol ``` --- diff --git a/hivemind_audio_binary_protocol/__init__.py b/hivemind_audio_binary_protocol/__init__.py new file mode 100644 index 0000000..8a37507 --- /dev/null +++ b/hivemind_audio_binary_protocol/__init__.py @@ -0,0 +1,474 @@ +import base64 +import queue +import subprocess +import threading +from dataclasses import dataclass, field +from queue import Queue +from shutil import which +from tempfile import NamedTemporaryFile +from typing import Dict, List, Tuple, Optional, Union + +import click +import speech_recognition as sr +from ovos_bus_client import MessageBusClient +from ovos_bus_client.message import Message +from ovos_plugin_manager.stt import OVOSSTTFactory +from ovos_plugin_manager.templates.microphone import Microphone +from ovos_plugin_manager.templates.stt import STT +from ovos_plugin_manager.templates.transformers import AudioLanguageDetector +from ovos_plugin_manager.templates.tts import TTS +from ovos_plugin_manager.templates.vad import VADEngine +from ovos_plugin_manager.tts import OVOSTTSFactory +from ovos_plugin_manager.vad import OVOSVADFactory +from ovos_plugin_manager.wakewords import OVOSWakeWordFactory +from ovos_simple_listener import SimpleListener, ListenerCallbacks +from ovos_utils.fakebus import FakeBus +from ovos_utils.log import LOG +from ovos_utils.xdg_utils import xdg_data_home + +from hivemind_bus_client.message import HiveMessage, HiveMessageType, HiveMindBinaryPayloadType +from hivemind_core.database import ClientDatabase +from hivemind_core.protocol import HiveMindListenerProtocol, HiveMindClientConnection +from hivemind_core.scripts import get_db_kwargs +from hivemind_core.service import HiveMindService + + +def bytes2audiodata(data: bytes) -> sr.AudioData: + """ + Convert raw audio bytes into `speech_recognition.AudioData`. + + Args: + data: Raw audio bytes. + + Returns: + An AudioData object representing the audio data. + """ + recognizer = sr.Recognizer() + with NamedTemporaryFile() as fp: + fp.write(data) + ffmpeg = which("ffmpeg") + if ffmpeg: + p = fp.name + "converted.wav" + # ensure file format + cmd = [ffmpeg, "-i", fp.name, "-acodec", "pcm_s16le", "-ar", + "16000", "-ac", "1", "-f", "wav", p, "-y"] + subprocess.call(cmd) + else: + LOG.warning("ffmpeg not found, please ensure audio is in a valid format") + p = fp.name + + with sr.AudioFile(p) as source: + audio = recognizer.record(source) + return audio + + +class HMCallbacks(ListenerCallbacks): + """ + Callbacks for handling various stages of audio recognition + """ + + def __init__(self, bus: Optional[Union[MessageBusClient, FakeBus]] = None) -> None: + """ + Initialize the HiveMind Callbacks. + + Args: + bus: The message bus client or a FakeBus for testing. + """ + self.bus = bus or FakeBus() + + def listen_callback(cls): + """ + Callback triggered when listening starts. + """ + LOG.info("New loop state: IN_COMMAND") + cls.bus.emit(Message("mycroft.audio.play_sound", + {"uri": "snd/start_listening.wav"})) + cls.bus.emit(Message("recognizer_loop:wakeword")) + cls.bus.emit(Message("recognizer_loop:record_begin")) + + def end_listen_callback(cls): + """ + Callback triggered when listening ends. + """ + LOG.info("New loop state: WAITING_WAKEWORD") + cls.bus.emit(Message("recognizer_loop:record_end")) + + def error_callback(cls, audio: sr.AudioData): + """ + Callback triggered when an error occurs during STT processing. + + Args: + audio: The audio data that caused the error. + """ + LOG.error("STT Failure") + cls.bus.emit(Message("recognizer_loop:speech.recognition.unknown")) + + def text_callback(cls, utterance: str, lang: str): + """ + Callback triggered when text is successfully transcribed. + + Args: + utterance: The transcribed text. + lang: The language of the transcription. + """ + LOG.info(f"STT: {utterance}") + cls.bus.emit(Message("recognizer_loop:utterance", + {"utterances": [utterance], "lang": lang})) + + +@dataclass +class FakeMicrophone(Microphone): + """ + A async implementation of a Microphone from a client connection. + """ + queue: "Queue[Optional[bytes]]" = field(default_factory=Queue) + _is_running: bool = False + sample_rate: int = 16000 + sample_width: int = 2 + sample_channels: int = 1 + chunk_size: int = 4096 + + def start(self) -> None: + """ + Start the microphone + """ + self._is_running = True + + def read_chunk(self) -> Optional[bytes]: + """ + Read a chunk of audio data from the queue. + + Returns: + A chunk of audio data or None if the queue is empty. + """ + try: + return self.queue.get(timeout=0.5) + except queue.Empty: + return None + except Exception as e: + LOG.exception(e) + return None + + def stop(self) -> None: + """ + Stop the microphone + """ + self._is_running = False + while not self.queue.empty(): + self.queue.get() + self.queue.put_nowait(None) + + +@dataclass +class PluginOptions: + """ + Configuration for plugins used in the listener. + """ + wakeword: str = "hey_mycroft" + tts: TTS = field(default_factory=OVOSTTSFactory.create) + stt: STT = field(default_factory=OVOSSTTFactory.create) + vad: VADEngine = field(default_factory=OVOSVADFactory.create) + lang_detector: Optional[AudioLanguageDetector] = None # TODO: Implement language detection. + + +class AudioReceiverProtocol(HiveMindListenerProtocol): + """ + Protocol for receiving and processing audio data in HiveMind. + """ + listeners: Dict[str, SimpleListener] = {} + plugin_opts: PluginOptions = None + + @property + def plugins(self) -> PluginOptions: + """ + Lazily load and return the plugin options. + + Returns: + The loaded PluginOptions instance. + """ + if not self.plugin_opts: + # lazy load + self.plugin_opts = PluginOptions() + return self.plugin_opts + + def add_listener(self, client: HiveMindClientConnection) -> None: + """ + Create and start a new listener for a connected client. + + Args: + client: The HiveMind client connection. + """ + LOG.info(f"Creating listener for peer: {client.peer}") + bus = FakeBus() + bus.connected_event = threading.Event() # TODO missing in FakeBus + bus.connected_event.set() + + def on_msg(m: str): + m: Message = Message.deserialize(m) + hm: HiveMessage = HiveMessage(HiveMessageType.BUS, payload=m) + client.send(hm) # forward listener messages to the client + if m.msg_type == "recognizer_loop:utterance": + self.handle_message(hm, client) # process it as if it came from the client + + bus.on("message", on_msg) + + AudioReceiverProtocol.listeners[client.peer] = SimpleListener( + mic=FakeMicrophone(), + vad=self.plugins.vad, + wakeword=OVOSWakeWordFactory.create_hotword(self.plugins.wakeword), # TODO allow different per client + stt=self.plugins.stt, + callbacks=HMCallbacks(bus) + ) + AudioReceiverProtocol.listeners[client.peer].start() + + @classmethod + def stop_listener(cls, client: HiveMindClientConnection) -> None: + """ + Stop and remove a listener for a disconnected client. + + Args: + client: The HiveMind client connection. + """ + if client.peer in AudioReceiverProtocol.listeners: + LOG.info(f"Stopping listener for key: {client.peer}") + AudioReceiverProtocol.listeners[client.peer].stop() + AudioReceiverProtocol.listeners.pop(client.peer) + + def handle_client_disconnected(self, client: HiveMindClientConnection) -> None: + """ + Handle a client disconnection event. + + Args: + client: The HiveMind client connection. + """ + super().handle_client_disconnected(client) + self.stop_listener(client) + + def get_tts(self, message: Optional[Message] = None) -> str: + """ + Generate TTS audio for the given utterance. + + Args: + message (Message, optional): A Mycroft Message object containing the 'utterance' key. + + Returns: + str: Path to the generated audio file. + """ + utterance = message.data['utterance'] + ctxt = self.plugins.tts._get_ctxt({"message": message}) + wav, _ = self.plugins.tts.synth(utterance, ctxt) + return str(wav) + + def get_b64_tts(self, message: Optional[Message] = None) -> str: + """ + Generate TTS audio and return it as a Base64-encoded string. + + Args: + message (Message, optional): A Mycroft Message object containing the 'utterance' key. + + Returns: + str: Base64-encoded TTS audio data. + """ + wav = self.get_tts(message) + # cast to str() to get a path, as it is a AudioFile object from tts cache + with open(wav, "rb") as f: + audio = f.read() + return base64.b64encode(audio).decode("utf-8") + + def transcribe_b64_audio(self, message: Optional[Message] = None) -> List[Tuple[str, float]]: + """ + Transcribe Base64-encoded audio data. + + Args: + message (Message, optional): A Mycroft Message object containing 'audio' (Base64) and optional 'lang'. + + Returns: + List[Tuple[str, float]]: List of transcribed utterances with confidence scores. + """ + b64audio = message.data["audio"] + lang = message.data.get("lang", self.plugins.stt.lang) + wav_data = base64.b64decode(b64audio) + audio = bytes2audiodata(wav_data) + utterances = self.plugins.stt.transcribe(audio, lang) + return utterances + + def handle_microphone_input(self, bin_data: bytes, sample_rate: int, sample_width: int, + client: HiveMindClientConnection) -> None: + """ + Handle binary audio data input from the microphone. + + Args: + bin_data (bytes): Raw audio data. + sample_rate (int): Sample rate of the audio. + sample_width (int): Sample width of the audio. + client (HiveMindClientConnection): Connection object for the client sending the data. + """ + if client.peer not in self.listeners: + self.add_listener(client) + m: FakeMicrophone = self.listeners[client.peer].mic + if m.sample_rate != sample_rate or m.sample_width != sample_width: + LOG.debug(f"Got {len(bin_data)} bytes of audio data from {client.peer}") + LOG.error(f"Sample rate/width mismatch! Got: ({sample_rate}, {sample_width}), " + f"expected: ({m.sample_rate}, {m.sample_width})") + # TODO - convert sample_rate if needed + else: + m.queue.put(bin_data) + + def handle_stt_transcribe_request(self, bin_data: bytes, sample_rate: int, sample_width: int, lang: str, + client: HiveMindClientConnection) -> None: + """ + Handle STT transcription request from binary audio data. + + Args: + bin_data (bytes): Raw audio data. + sample_rate (int): Sample rate of the audio. + sample_width (int): Sample width of the audio. + lang (str): Language of the audio. + client (HiveMindClientConnection): Connection object for the client sending the data. + """ + LOG.debug(f"Received binary STT input: {len(bin_data)} bytes") + audio = sr.AudioData(bin_data, sample_rate, sample_width) + tx = self.plugins.stt.transcribe(audio, lang) + m = Message("recognizer_loop:transcribe.response", {"transcriptions": tx, "lang": lang}) + client.send(HiveMessage(HiveMessageType.BUS, payload=m)) + + def handle_stt_handle_request(self, bin_data: bytes, sample_rate: int, sample_width: int, lang: str, + client: HiveMindClientConnection) -> None: + """ + Handle STT utterance transcription and injection into the message bus. + + Args: + bin_data (bytes): Raw audio data. + sample_rate (int): Sample rate of the audio. + sample_width (int): Sample width of the audio. + lang (str): Language of the audio. + client (HiveMindClientConnection): Connection object for the client sending the data. + """ + LOG.debug(f"Received binary STT input: {len(bin_data)} bytes") + audio = sr.AudioData(bin_data, sample_rate, sample_width) + tx = self.plugins.stt.transcribe(audio, lang) + if tx: + utts = [t[0].rstrip(" '\"").lstrip(" '\"") for t in tx] + m = Message("recognizer_loop:utterance", + {"utterances": utts, "lang": lang}) + self.handle_inject_mycroft_msg(m, client) + else: + LOG.info(f"STT transcription error for client: {client.peer}") + m = Message("recognizer_loop:speech.recognition.unknown") + client.send(HiveMessage(HiveMessageType.BUS, payload=m)) + + def handle_inject_mycroft_msg(self, message: Message, client: HiveMindClientConnection) -> None: + """ + Handle injection of Mycroft bus messages into the HiveMind system. + + Args: + message (Message): Mycroft bus message object. + client (HiveMindClientConnection): Connection object for the client receiving the response. + """ + if message.msg_type == "speak:synth": + wav = self.get_tts(message) + with open(wav, "rb") as f: + bin_data = f.read() + payload = HiveMessage(HiveMessageType.BINARY, + payload=bin_data, + metadata={"lang": message.data["lang"], + "file_name": wav.split("/")[-1], + "utterance": message.data["utterance"]}, + bin_type=HiveMindBinaryPayloadType.TTS_AUDIO) + client.send(payload) + return + elif message.msg_type == "speak:b64_audio": + msg: Message = message.reply("speak:b64_audio.response", message.data) + msg.data["audio"] = self.get_b64_tts(message) + if msg.context.get("destination") is None: + msg.context["destination"] = "audio" # ensure not treated as a broadcast + payload = HiveMessage(HiveMessageType.BUS, msg) + client.send(payload) + return + elif message.msg_type == "recognizer_loop:b64_transcribe": + msg: Message = message.reply("recognizer_loop:b64_transcribe.response", + {"lang": message.data["lang"]}) + msg.data["transcriptions"] = self.transcribe_b64_audio(message) + if msg.context.get("destination") is None: + msg.context["destination"] = "skills" # ensure not treated as a broadcast + payload = HiveMessage(HiveMessageType.BUS, msg) + client.send(payload) + return + elif message.msg_type == "recognizer_loop:b64_audio": + transcriptions = self.transcribe_b64_audio(message) + msg: Message = message.forward("recognizer_loop:utterance", + {"utterances": [u[0] for u in transcriptions], + "lang": self.stt.lang}) + super().handle_inject_mycroft_msg(msg, client) + else: + super().handle_inject_mycroft_msg(message, client) + + +@click.command() +@click.option('--wakeword', default="hey_mycroft", + help="Specify the wake word for the listener. Default is 'hey_mycroft'. config will be loaded from mycroft.conf.") +@click.option('--stt-plugin', default=None, + help="Specify the STT plugin to use. If not provided, the default STT from mycroft.conf will be used.") +@click.option('--tts-plugin', default=None, + help="Specify the TTS plugin to use. If not provided, the default TTS from mycroft.conf will be used.") +@click.option('--vad-plugin', default=None, + help="Specify the VAD plugin to use. If not provided, the default VAD from mycroft.conf will be used.") +@click.option("--ovos_bus_address", help="Open Voice OS bus address", type=str, default="127.0.0.1") +@click.option("--ovos_bus_port", help="Open Voice OS bus port number", type=int, default=8181) +@click.option("--host", help="HiveMind host", type=str, default="0.0.0.0") +@click.option("--port", help="HiveMind port number", type=int, required=False) +@click.option("--ssl", help="use wss://", type=bool, default=False) +@click.option("--cert_dir", help="HiveMind SSL certificate directory", type=str, default=f"{xdg_data_home()}/hivemind") +@click.option("--cert_name", help="HiveMind SSL certificate file name", type=str, default="hivemind") +@click.option("--db-backend", type=click.Choice(['redis', 'json', 'sqlite'], case_sensitive=False), default='json', + help="Select the database backend to use. Options: redis, sqlite, json.") +@click.option("--db-name", type=str, default="clients", + help="[json/sqlite] The name for the database file. ~/.cache/hivemind-core/{name}") +@click.option("--db-folder", type=str, default="hivemind-core", + help="[json/sqlite] The subfolder where database files are stored. ~/.cache/{db_folder}}") +@click.option("--redis-host", default="localhost", help="[redis] Host for Redis. Default is localhost.") +@click.option("--redis-port", default=6379, help="[redis] Port for Redis. Default is 6379.") +@click.option("--redis-password", required=False, help="[redis] Password for Redis. Default None") +def run_hivemind_listener(wakeword, stt_plugin, tts_plugin, vad_plugin, + ovos_bus_address: str, ovos_bus_port: int, host: str, port: int, + ssl: bool, cert_dir: str, cert_name: str, + db_backend, db_name, db_folder, + redis_host, redis_port, redis_password + ): + """ + Run the HiveMind Listener with configurable plugins. + """ + kwargs = get_db_kwargs(db_backend, db_name, db_folder, redis_host, redis_port, redis_password) + ovos_bus_config = { + "host": ovos_bus_address or "127.0.0.1", + "port": ovos_bus_port or 8181, + } + + websocket_config = { + "host": host, + "port": port or 5678, + "ssl": ssl or False, + "cert_dir": cert_dir, + "cert_name": cert_name, + } + + # Configure wakeword, TTS, STT, and VAD plugins + AudioReceiverProtocol.plugin_opts = PluginOptions( + wakeword=wakeword, + stt=OVOSSTTFactory.create(stt_plugin) if stt_plugin else OVOSSTTFactory.create(), + tts=OVOSTTSFactory.create(tts_plugin) if tts_plugin else OVOSTTSFactory.create(), + vad=OVOSVADFactory.create(vad_plugin) if vad_plugin else OVOSVADFactory.create() + ) + + # Start the service + click.echo(f"Starting HiveMind Listener with wakeword '{wakeword}'...") + service = HiveMindService( + ovos_bus_config=ovos_bus_config, + websocket_config=websocket_config, + db=ClientDatabase(**kwargs), + protocol=AudioReceiverProtocol) + service.run() + + +if __name__ == "__main__": + run_hivemind_listener() diff --git a/hivemind_listener/protocol.py b/hivemind_audio_binary_protocol/protocol.py similarity index 98% rename from hivemind_listener/protocol.py rename to hivemind_audio_binary_protocol/protocol.py index 78e9e5d..f8127e1 100644 --- a/hivemind_listener/protocol.py +++ b/hivemind_audio_binary_protocol/protocol.py @@ -28,9 +28,9 @@ from hivemind_bus_client.message import HiveMessage, HiveMessageType, HiveMindBinaryPayloadType from hivemind_core.protocol import HiveMindClientConnection -from hivemind_listener.transformers import (DialogTransformersService, - MetadataTransformersService, - UtteranceTransformersService) +from hivemind_audio_binary_protocol.transformers import (DialogTransformersService, + MetadataTransformersService, + UtteranceTransformersService) from hivemind_plugin_manager.protocols import BinaryDataHandlerProtocol, ClientCallbacks diff --git a/hivemind_listener/transformers.py b/hivemind_audio_binary_protocol/transformers.py similarity index 100% rename from hivemind_listener/transformers.py rename to hivemind_audio_binary_protocol/transformers.py diff --git a/hivemind_listener/version.py b/hivemind_audio_binary_protocol/version.py similarity index 52% rename from hivemind_listener/version.py rename to hivemind_audio_binary_protocol/version.py index d0ee642..cbb1e3f 100644 --- a/hivemind_listener/version.py +++ b/hivemind_audio_binary_protocol/version.py @@ -1,6 +1,6 @@ # START_VERSION_BLOCK -VERSION_MAJOR = 3 -VERSION_MINOR = 0 +VERSION_MAJOR = 0 +VERSION_MINOR = 1 VERSION_BUILD = 0 -VERSION_ALPHA = 1 +VERSION_ALPHA = 0 # END_VERSION_BLOCK diff --git a/hivemind_listener/__init__.py b/hivemind_listener/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/requirements.txt b/requirements.txt index e9d870a..12fe76b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ ovos-simple-listener +hivemind_bus_client>=0.1.0,<1.0.0 ovos-plugin-manager<1.0.0 -hivemind-plugin-manager>=0.3.0,<1.0.0 -pybase64 \ No newline at end of file +hivemind-core>=0.1.0,<1.0.0 +click \ No newline at end of file diff --git a/setup.py b/setup.py index ac7ec00..cb7870a 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ def get_version(): """Find the version of the package""" - version_file = os.path.join(BASEDIR, "hivemind_listener", "version.py") + version_file = os.path.join(BASEDIR, "hivemind_audio_binary_protocol", "version.py") major, minor, build, alpha = (None, None, None, None) with open(version_file) as f: for line in f: @@ -38,15 +38,15 @@ def required(requirements_file): ] return [pkg for pkg in requirements if pkg.strip() and not pkg.startswith("#")] -PLUGIN_ENTRY_POINT = 'hivemind-audio-binary-protocol-plugin=hivemind_listener.protocol:AudioBinaryProtocol' +PLUGIN_ENTRY_POINT = 'hivemind-audio-binary-protocol-plugin=hivemind_audio_binary_protocol.protocol:AudioBinaryProtocol' setup( - name="hivemind-listener", + name="hivemind-audio-binary-protocol", version=get_version(), - packages=["hivemind_listener"], + packages=["hivemind_audio_binary_protocol"], include_package_data=True, install_requires=required("requirements.txt"), - url="https://github.com/JarbasHiveMind/HiveMind-listener", + url="https://github.com/JarbasHiveMind/hivemind-audio-binary-protocol", license="Apache2.0", author="jarbasAI", author_email="jarbasai@mailfence.com",