Compare commits

...

124 Commits

Author SHA1 Message Date
gitea b4d1a7b321 CLock 2024-09-20 18:28:17 +02:00
gitea aec60b217c Clock tests 2024-09-20 18:24:40 +02:00
gitea ef0be53a96 Fixing debug levels 2024-09-20 18:19:57 +02:00
gitea 3398d5df92 test 2024-09-20 18:08:45 +02:00
gitea e5d202ad3d Last łan for todej mejbi 2024-09-20 17:53:48 +02:00
gitea 71e5462617 Fur ktosia! 2024-09-20 17:44:01 +02:00
gitea 8e6f562fd4 test 2024-09-20 17:41:39 +02:00
gitea 58617dc410 tesst 2024-09-20 17:37:17 +02:00
gitea c7e02c48ae Masha wants to code 2024-09-20 17:32:20 +02:00
gitea 77e22e6cb7 fx 2024-09-20 17:31:26 +02:00
gitea 1cc7c0b51c fx 2024-09-20 17:29:50 +02:00
gitea 94fe448326 fx 2024-09-20 17:25:31 +02:00
gitea 66906c4486 fx 2024-09-20 17:23:08 +02:00
gitea 62d60218db test 2024-09-20 17:19:17 +02:00
gitea 28c5f36cb0 fx 2024-09-20 17:12:38 +02:00
gitea 024c42a800 fx 2024-09-20 17:07:37 +02:00
gitea 590ab187d8 Bugfix 2024-09-20 17:04:16 +02:00
gitea 80c69a8823 ass 2024-09-20 17:02:21 +02:00
gitea 61a6d1f530 SAy 2024-09-20 16:57:21 +02:00
gitea a1cfa1852e Test 2024-09-20 16:49:41 +02:00
gitea 422209c3f5 fx 2024-09-20 16:44:13 +02:00
gitea 81c477999f fx 2024-09-20 16:42:10 +02:00
gitea c714bdb2d7 async 2024-09-20 16:30:43 +02:00
gitea 26c34c6fa9 asynv 2024-09-20 16:26:01 +02:00
gitea f1d73fc0a1 tst 2024-09-20 16:14:45 +02:00
gitea d027a961b7 fx 2024-09-20 16:08:43 +02:00
gitea 0278dc3a49 Fx test 2024-09-20 16:06:05 +02:00
gitea 4184cbb687 Fix 2024-09-20 15:48:00 +02:00
gitea 88926ec627 Fx 2024-09-20 14:42:18 +02:00
gitea e628b18354 asa 2024-09-20 14:27:46 +02:00
gitea b025790b3d Thread 2024-09-20 14:19:16 +02:00
gitea b3121aaaf2 ii 2024-09-20 13:41:14 +02:00
gitea 2e13593433 tst 2 2024-09-20 13:37:52 +02:00
gitea b78f542778 1 2024-09-20 13:36:00 +02:00
gitea 39865253fe tst 2024-09-20 13:33:52 +02:00
gitea b2e414a268 tst 2024-09-20 13:23:27 +02:00
gitea e3ad25b95e Fix asyncio sleep 2024-09-20 13:21:20 +02:00
gitea 62bb650b37 Fix fo hack 2024-09-20 13:18:36 +02:00
gitea dbca290979 Hack na hacku hackiem pogania 2024-09-20 13:16:18 +02:00
gitea 0ed0f60b9f fx 2024-09-20 00:49:06 +02:00
gitea 02e8d4effd fix 2024-09-20 00:45:19 +02:00
gitea 9c686ded98 aaa 2024-09-20 00:41:50 +02:00
gitea 6a50568d25 fx 2024-09-20 00:34:41 +02:00
gitea a60e99cd6e fix 2024-09-20 00:31:48 +02:00
gitea cc5f97f1d6 test 2024-09-20 00:30:04 +02:00
gitea 0a248ef3fb fix 2024-09-20 00:27:11 +02:00
gitea 582ae365c7 Fa 2024-09-20 00:24:20 +02:00
gitea fad17ef9a8 test 2024-09-20 00:21:49 +02:00
gitea 945ed5677b FX 2024-09-19 23:25:41 +02:00
gitea 6c68b574a2 aa 2024-09-19 23:01:36 +02:00
gitea 3c177b7538 Fix 2024-09-19 22:59:12 +02:00
gitea 1419be48a1 FX 2024-09-19 21:57:40 +02:00
gitea 80360e0dde Fixtime 2024-09-19 21:49:03 +02:00
gitea 48fce96250 Test 2024-09-19 21:09:05 +02:00
gitea e1a38bcdcd fa 2024-09-19 20:47:14 +02:00
gitea 529c8ba20a addfs 2024-09-19 20:44:36 +02:00
gitea 2a87153e71 fx 2024-09-19 20:34:26 +02:00
gitea 4a769006a8 fx 2024-09-19 20:32:18 +02:00
gitea 13029b25b5 :) 2024-09-19 20:27:52 +02:00
gitea 9567cb07e2 fx 2024-09-19 20:22:05 +02:00
gitea 33ded0ffe9 zzz 2024-09-19 20:18:46 +02:00
gitea a4dceb8651 as 2024-09-19 20:14:48 +02:00
gitea ada24f505d aa 2024-09-19 20:11:36 +02:00
gitea 28db612fad ff 2024-09-19 19:31:01 +02:00
gitea 520d995553 Fx 2024-09-19 19:14:20 +02:00
gitea 49f393fc47 aa 2024-09-19 19:09:44 +02:00
gitea 0ce3710b29 Fx 2024-09-19 18:26:18 +02:00
gitea cb8ad5ca08 fgf 2024-09-19 18:13:36 +02:00
gitea 373ea608d9 fx 2024-09-19 18:04:18 +02:00
gitea 9052e3e505 aaaa 2024-09-19 18:00:00 +02:00
gitea ad2a1a21e2 :) 2024-09-19 17:55:50 +02:00
gitea e1e0a3569b :S 2024-09-19 17:52:30 +02:00
gitea 2f183c3afb fx 2024-09-19 17:48:41 +02:00
gitea 1b0695d6c2 FX 2024-09-19 17:45:23 +02:00
gitea 1a31175db9 aaa 2024-09-19 17:42:34 +02:00
gitea e34dc37dc1 fx 2024-09-19 17:35:23 +02:00
gitea 2646178a28 fx 2024-09-19 17:30:43 +02:00
gitea dd0b679bfc fx 2024-09-19 16:55:09 +02:00
gitea 73b7a4bfa4 fx 2024-09-19 16:45:36 +02:00
gitea 8b134ddcc9 Fix 2024-09-19 16:44:09 +02:00
gitea 6011ab5cd4 fx 2024-09-19 16:39:22 +02:00
gitea 5697f057d9 fx 2024-09-19 16:37:27 +02:00
gitea 2d43f6a658 Revert 2024-09-19 12:59:01 +02:00
gitea c6c1c2f25d Fix 2024-09-19 12:55:18 +02:00
gitea 3930b940e8 Fix 2024-09-19 12:53:38 +02:00
gitea 12efaceaf6 TR 2024-09-19 12:51:43 +02:00
gitea d3ac0ed0b1 A 2024-09-19 12:43:12 +02:00
gitea 554852bc53 11 2024-09-19 12:40:30 +02:00
gitea 7f57da86f0 fix 2024-09-18 00:24:53 +02:00
gitea acfda1e29e fx2 2024-09-17 23:21:45 +02:00
gitea 94208918d5 fx1 2024-09-17 23:19:56 +02:00
gitea cea6a37de5 Events 2024-09-17 23:18:09 +02:00
gitea 6c58ce90ac 2 2024-09-17 21:51:29 +02:00
gitea fddc6b1976 Fix 2024-09-17 21:50:32 +02:00
gitea 89f4b0e51e tst 2024-09-17 21:48:30 +02:00
gitea 781b83c9b1 msink 2024-09-17 17:19:00 +02:00
gitea 944bb7e5fa fx 2024-09-17 16:29:11 +02:00
gitea 7e4e8f167f Fsink wsink 2024-09-17 16:28:03 +02:00
gitea b59bd4c41d ffmpeg 2024-09-17 16:20:37 +02:00
gitea 6dd0e1865d saving 2024-09-17 16:15:03 +02:00
gitea b6cb5e03b7 Att 3 2024-09-17 16:07:42 +02:00
gitea 7fae6b601e Att2 2024-09-17 16:06:00 +02:00
gitea f9c854e2fc Test 2024-09-17 16:03:34 +02:00
gitea 169552e8d5 work 2024-08-28 14:57:50 +02:00
gitea c448201823 Testing for transcribe 2024-08-28 14:44:54 +02:00
gitea 7857226e12 Fix 2024-08-23 18:46:40 +02:00
gitea e6648926a6 Fx 2024-08-15 17:12:01 +02:00
gitea ce9beb5ecf fx 2024-08-15 16:54:08 +02:00
gitea 63fb7a3b8a fx 2024-08-15 16:51:24 +02:00
gitea fd23518f7a Tst 2024-08-15 16:46:42 +02:00
gitea 5ea792bcd2 Transcribe cog 2024-08-15 14:11:17 +02:00
gitea 1d520c4aeb Additions before wsl purge 2024-08-15 10:52:29 +02:00
gitea dbcb8cce27 Testing transcribe 2024-08-13 12:32:02 +02:00
gitea 215a36f91e Fx 2024-08-09 17:10:14 +02:00
gitea ecabfc8876 fx 2024-08-09 17:06:01 +02:00
gitea 20f045e92a fx 2024-08-09 17:03:15 +02:00
gitea c90e90ea10 Port fix 2024-08-09 17:02:05 +02:00
gitea fcefaf11ef Fixing time mismatch 2024-08-09 16:59:33 +02:00
gitea 8c6e80d832 Fix 2024-08-09 16:53:17 +02:00
gitea 96b93488c7 1 2024-08-09 16:47:35 +02:00
gitea c31390859f Metadata 2024-08-09 16:43:52 +02:00
gitea 314a77c1f9 Fx 2024-08-05 11:24:13 +02:00
gitea 0345573cb8 fx 2024-08-03 16:38:42 +02:00
gitea 4842ccdc22 Music trackin upgrade 2024-08-03 15:34:56 +02:00
12 changed files with 670 additions and 28 deletions
+20 -11
View File
@@ -122,7 +122,6 @@ class MusicFileList(object):
# *=========================================== Predefines
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
MASTER_TIMEOUT = datetime.now()
INITIAL_TIME_WAIT = 500
MUZYKA: Music_Config = {"ctx": None, "queue": [], "requester": []}
@@ -243,6 +242,7 @@ handler = handlers.RotatingFileHandler(
maxBytes=6 * 1024 * 1024,
backupCount=6,
)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
random.seed()
@@ -301,8 +301,13 @@ logger.info("Done")
async def on_ready():
"""Metoda wywoływana przy połączeniu do serwera."""
logger.debug("%s has connected to Discord!", client.user)
await client.load_extension("voice_recognition")
logger.info("Cogs")
logger.info(client.cogs)
await client.change_presence(activity=discord.Game(name="Axe Throwing Darts"))
await client.tree.sync()
for com in client.commands:
logger.info(com.qualified_name)
check_self.start()
check_data_q.start()
@@ -537,16 +542,20 @@ async def connect(ctx, arg=None):
"""
if ctx and arg:
logger.info("Ctx and arg defined for connect")
voice_channel = client.get_channel(1060349757349974066)
if client.voice_clients:
voice_client = client.voice_clients[0]
if voice_client.is_connected():
logger.info("Already connected with other client")
else:
vc = None
if ctx.author.voice.channel:
vc = await ctx.author.voice.channel.connect()
else:
voice_channel = client.get_channel(1060349757349974066)
vc = await voice_channel.connect()
if not vc:
logger.error("Not possible to connect to voice")
return
voice_channel_connection = await voice_channel.connect()
if not voice_channel_connection:
logger.error("Not possible to connect to voice")
logger.info("Connecting to voice")
logger.info("Connected to voice")
async def disconnect(ctx):
@@ -2389,7 +2398,7 @@ async def wyszukaj_linki_do_dokumentow(ctx):
username = ctx.message.author.nick
else:
username = ctx.message.author.name
query_object = QueryControl(username, query_uuid, query, logger, ctx)
query_object = QueryControl(username, query_uuid, query, ctx)
OUT_COMM_Q.put(query_object)
await ctx.send(
f"No dobra poszło. Zapamiętaj proszę {query_uuid} - to identyfikator twojego zapytania. Jesteś {queue_size} w kolejce."
@@ -2484,7 +2493,7 @@ async def wyszukaj_linki_do_dokumentow_deep(ctx):
username = ctx.message.author.nick
else:
username = ctx.message.author.name
query_object = QueryControl(username, query_uuid, query, logger, ctx)
query_object = QueryControl(username, query_uuid, query, ctx)
OUT_COMM_Q.put(query_object)
await ctx.send(
f"Wypij wypi {query_uuid} - to identyfikator twojego zapytania. Jesteś {queue_size} w kolejce niestety kolejka obowiazuje zawsze."
@@ -2543,7 +2552,7 @@ async def co_na_plejliscie_wariacie(ctx):
async with ctx.typing():
logger.info("plejlista")
logger.info(PREPPED_TRACKS)
await ctx.send(f"Didżej Hammer i jego sztuczna inteligencja (||he he||) prezentują: \n- obecnie grany jest {(PREPPED_TRACKS['now_playing']).rstrip()} \n- na liście priorytetowej {(PREPPED_TRACKS['priority']).rstrip()} \n- na liście hitów obecnie czeka kawałek {(PREPPED_TRACKS['hit']).rstrip()} \n- na liście wszystkich kawałków {(PREPPED_TRACKS['all']).rstrip()} \n- najświeższym zamówieniem od słuchaczy jest {(PREPPED_TRACKS['requests']).rstrip()} \nZ kolei jingiel to: {(PREPPED_TRACKS['jingles']).rstrip()}")
await ctx.send(f"Didżej Hammer i jego sztuczna inteligencja (||he he||) prezentują: \n- obecnie grany jest {(PREPPED_TRACKS['now_playing']).rstrip()} (wg metadanych: {PREPPED_TRACKS['meta']}) \n- następny będzie{(PREPPED_TRACKS['next']).rstrip()} \n- na liście priorytetowej {(PREPPED_TRACKS['priority']).rstrip()} \n- na liście hitów obecnie czeka kawałek {(PREPPED_TRACKS['hit']).rstrip()} \n- na liście wszystkich kawałków {(PREPPED_TRACKS['all']).rstrip()} \n- najświeższym zamówieniem od słuchaczy jest {(PREPPED_TRACKS['requests']).rstrip()} \nZ kolei jingiel to: {(PREPPED_TRACKS['jingles']).rstrip()}")
# *================================== Run
if __name__ == "__main__":
+42 -5
View File
@@ -2,14 +2,20 @@ import logging
import threading
import json
import time
import re
from urllib import request as urequest
from queue import Queue, Empty
from flask import Flask, jsonify, request
from waitress import serve
HOST_ADDRESS = "192.168.1.191"
PORT_ADDRESS = 5000
ICECAST_ADDRESS = "http://192.168.1.15:8000"
OUT_COMM_Q = Queue()
IN_COMM_Q = Queue()
SRCHTITLE = re.compile(br'StreamTitle=\\*(?P<title>[^;]*);').search
awaiting_q = []
incoming_q = Queue()
app = Flask(__name__)
@@ -19,7 +25,9 @@ PREPPED_TRACKS = {
"all": "",
"priority":"",
"jingles":"",
"now_playing":""
"now_playing":"",
"next": "",
"meta" : ""
}
logger = logging.getLogger("discord")
@@ -42,13 +50,20 @@ class QueryControl:
@app.route("/prepped_tracks", methods=["POST"])
def log_radio_tracks():
logger = logging.getLogger("discord")
app.logger = logging.getLogger("discord")
logger.info(request)
app.logger.info(request)
record = json.loads(request.data)
logger.info(record)
app.logger.info(record)
if "next" in record[0]:
metadata = id3(ICECAST_ADDRESS)
PREPPED_TRACKS["now_playing"] = PREPPED_TRACKS["next"]
if metadata:
PREPPED_TRACKS["meta"] = metadata["name"] + " - " + metadata["title"] + "(" + metadata["genre"] + ")"
else:
PREPPED_TRACKS["meta"] = "Nie znaju"
PREPPED_TRACKS[record[0]] = record[1]
logger.info("DATA RECEIVED")
app.logger.info("DATA RECEIVED")
return jsonify("SUCCESS")
@@ -147,6 +162,28 @@ def scan_incoming():
except Empty:
time.sleep(1)
def get_stream_title(tag:bytes) -> str:
title = ''
if m := SRCHTITLE(tag):
#decode, strip, unescape and remove surrounding quotes (may not even be the same type of quote)
title = m.group('title').decode('utf-8').strip().replace('\\', '')[1:-1]
return title
def id3(url:str) -> dict:
request = urequest.Request(url, headers={'Icy-MetaData': 1})
with urequest.urlopen(request) as resp:
metaint = int(resp.headers.get('icy-metaint', '-1'))
if metaint<0:
return False
resp.read(metaint) #this isn't seekable so, arbitrarily read to the point we want
tagdata = dict(
site_url = resp.headers.get('icy-url' ) ,
name = resp.headers.get('icy-name' ).title(),
genre = resp.headers.get('icy-genre').title(),
title = get_stream_title(resp.read(255)) )
return tagdata
def comm_subroutine():
"""
+5 -2
View File
@@ -123,7 +123,7 @@ def scan_tracks():
time.sleep(0.1)
file1 = open(PERSISTENCE_PATH, 'r')
lines = file1.readlines()
result = ["now_playing", lines[2]]
result = ["next", lines[2]]
file1.close()
returned = requests.post(
f"{MAIN_BOT_ADDRESS}{MUSIC_TRACKER}",
@@ -568,10 +568,13 @@ if __name__ == "__main__":
# threads.append(threading.Thread(target=flask_debug))
threads.append(threading.Thread(target=waitress_run))
threads.append(threading.Thread(target=thread_rescan))
threads.append(threading.Thread(target=scan_tracks))
for worker in threads:
worker.start()
time.sleep(60)
threads.append(threading.Thread(target=scan_tracks))
threads[2].start()
for worker in threads:
worker.join()
+11 -9
View File
@@ -64,21 +64,23 @@ b = bass_boost(frequency=f, gain=g, s)
s = add([s, b])
# Set up interactive control for main volume
a = interactive.float("main_volume", min=0., max=20., 1.)
a = interactive.float("main_volume", min=0., max=20., 0.4)
s = compress.multiband.interactive(bands=7, s)
mic_gain = interactive.float("mic_volume", min=0., max=20., 6.)
# Apply audio processing effects
tmic = buffer(input.pulseaudio()) # Microphone
mic = amplify(mic_gain, tmic)
mic = gate(threshold=-80., range=-120., mic)
mic = compress(threshold=0., ratio=2.,mic)
mic = blank.strip(max_blank=10., min_noise=.1, threshold=-20., mic)
s = add([s, mic])
# Apply audio processing effects
s = nrj(normalize(s))
s = amplify(a, s)
mic = buffer(input.pulseaudio()) # Microphone
mic = gate(threshold=-30., range=-80., mic)
mic = blank.strip(max_blank=10., min_noise=.1, threshold=-20., mic)
s = add([s, mic])
#w = interactive.float("wetness", min=0., max=1., 1.)
#s = dry_wet(w,s,s2)
# Skip blank sections in the stream
s = blank.skip(max_blank=10., s)
+6
View File
@@ -1,4 +1,5 @@
#!/bin/bash
sudo apt-get install python3-dev
cd /home/pi
mdkir Conjurer
cd Conjurer
@@ -9,6 +10,11 @@ source ./env/bin/activate
./env/bin/python3 -m pip install -r requirements_bot.txt
sed -i -e 's/os.rename/shutil.copy/g' ./env/lib/python3.11/site-packages/spotify_dl/youtube.py
sed -i '1i\import shutil' ./env/lib/python3.11/site-packages/spotify_dl/youtube.py
sed -i '1i\import logging' ./env/lib/python3.11/site-packages/spotify_dl/spotify.py
sed -i '21i\ logger = logging.getLogger("discord")' ./env/lib/python3.11/site-packages/spotify_dl/spotify.py
sed -i '22i\ logger.info("Playlist")' ./env/lib/python3.11/site-packages/spotify_dl/spotify.py
deactivate
sudo cp ./conjurer.service /etc/systemd/system/
sudo systemctl daemon-reload
+4
View File
@@ -32,3 +32,7 @@ sudo cp /home/pi/conjurer/conjurer_musician/radio_service.service /etc/systemd/s
sudo systemctl daemon-reload
sudo systemctl start radio_service.service
sudo systemctl enable radio_service.service
sed -i -e 's/os.rename/shutil.copy/g' ./env/lib/python3.11/site-packages/spotify_dl/youtube.py
sed -i '1i\import shutil' ./env/lib/python3.11/site-packages/spotify_dl/youtube.py
+4 -1
View File
@@ -13,4 +13,7 @@ tiktoken
PyNaCl
flask
waitress
clickupython
clickupython
assemblyai[extras]
SpeechRecognition
git+https://github.com/imayhaveborkedit/discord-ext-voice-recv
+229
View File
@@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import logging
from discord.ext.voice_recv.sinks import AudioSink
log = logging.getLogger(__name__)
__all__ = [
'SpeechRecognitionSink',
]
try:
import discord.ext.speech_recognition as sr # type: ignore
except ImportError:
def SpeechRecognitionSink(**kwargs) -> AudioSink:
"""A stub for when the SpeechRecognition module isn't found."""
raise RuntimeError('The SpeechRecognition module is required to use this sink.')
else:
import time
import array
import asyncio
import audioop
from collections import defaultdict
from discord.ext.voice_recv.sinks import SilencePacket
from typing import TYPE_CHECKING, TypedDict
if TYPE_CHECKING:
from concurrent.futures import Future as CFuture
from typing import Literal, Callable, Optional, Any, Final, Protocol, Awaitable, TypeVar
from discord import Member
from ..opus import VoiceData
from ..types import MemberOrUser as User
T = TypeVar('T')
SRRecognizerMethod = Literal[
'sphinx',
'google',
'google_cloud',
'wit',
'azure',
'bing',
'lex',
'houndify',
'amazon',
'assemblyai',
'ibm',
'tensorflow',
'whisper',
'vosk',
]
class SRStopper(Protocol):
def __call__(self, wait: bool = True, /) -> None:
...
SRProcessDataCB = Callable[[sr.Recognizer, sr.AudioData, User], Optional[str]]
SRTextCB = Callable[[User, str], Any]
class _StreamData(TypedDict):
stopper: Optional[SRStopper]
recognizer: sr.Recognizer
buffer: array.array[int]
class SpeechRecognitionSink(AudioSink): # type: ignore
def __init__(
self,
*,
process_cb: Optional[SRProcessDataCB] = None,
text_cb: Optional[SRTextCB] = None,
default_recognizer: SRRecognizerMethod = 'assemblyai',
phrase_time_limit: int = 10,
ignore_silence_packets: bool = True,
):
super().__init__(None)
self.process_cb: Optional[SRProcessDataCB] = process_cb
self.text_cb: Optional[SRTextCB] = text_cb
self.phrase_time_limmit: int = phrase_time_limit
self.ignore_silence_packets: bool = ignore_silence_packets
self.default_recognizer: SRRecognizerMethod = default_recognizer
self._stream_data: defaultdict[int, _StreamData] = defaultdict(
lambda: _StreamData(stopper=None, recognizer=sr.Recognizer(), buffer=array.array('B'))
)
def _await(self, coro: Awaitable[T]) -> CFuture[T]:
assert self.client is not None
return asyncio.run_coroutine_threadsafe(coro, self.client.loop)
def wants_opus(self) -> bool:
return False
def write(self, user: Optional[User], data: VoiceData) -> None:
if self.ignore_silence_packets and isinstance(data.packet, SilencePacket):
return
if user is None:
return
sdata = self._stream_data[user.id]
sdata['buffer'].extend(data.pcm)
if not sdata['stopper']:
sdata['stopper'] = sdata['recognizer'].listen_in_background(
DiscordSRAudioSource(sdata['buffer']), self.background_listener(user), self.phrase_time_limmit
)
def background_listener(self, user: User):
process_cb = self.process_cb or self.get_default_process_callback()
text_cb = self.text_cb or self.get_default_text_callback()
def callback(_recognizer: sr.Recognizer, _audio: sr.AudioData):
output = process_cb(_recognizer, _audio, user)
if output is not None:
text_cb(user, output)
return callback
def get_default_process_callback(self) -> SRProcessDataCB:
def cb(recognizer: sr.Recognizer, audio: sr.AudioData, user: Optional[User]) -> Optional[str]:
log.debug("Got %s, %s, %s", audio, audio.sample_rate, audio.sample_width)
text: Optional[str] = None
try:
# they changed recognize_google to be optionally assigned at runtime...
func = getattr(recognizer, 'recognize_' + self.default_recognizer, recognizer.recognize_google) # type: ignore
text = func(audio) # type: ignore
except sr.UnknownValueError:
log.debug("Bad speech chunk")
# self._debug_audio_chunk(audio)
return text
return cb
def get_default_text_callback(self) -> SRTextCB:
def cb(user: Optional[User], text: Optional[str]) -> Any:
log.info("%s said: %s", user.display_name if user else 'Someone', text)
return cb
@AudioSink.listener()
def on_voice_member_disconnect(self, member: Member, ssrc: Optional[int]) -> None:
self._drop(member.id)
def cleanup(self) -> None:
for user_id in tuple(self._stream_data.keys()):
self._drop(user_id)
def _drop(self, user_id: int) -> None:
data = self._stream_data.pop(user_id)
stopper = data.get('stopper')
if stopper:
stopper()
buffer = data.get('buffer')
if buffer:
# arrays don't have a clear function
del buffer[:]
def _debug_audio_chunk(self, audio: sr.AudioData, filename: str = 'sound.wav') -> None:
import io, wave, discord
with io.BytesIO() as b:
with wave.open(b, 'wb') as writer:
writer.setframerate(48000)
writer.setsampwidth(2)
writer.setnchannels(2)
writer.writeframes(audio.get_wav_data())
b.seek(0)
f = discord.File(b, filename)
self._await(self.voice_client.channel.send(file=f)) # type: ignore
class DiscordSRAudioSource(sr.AudioSource):
little_endian: Final[bool] = True
SAMPLE_RATE: Final[int] = 48_000
SAMPLE_WIDTH: Final[int] = 2
CHANNELS: Final[int] = 2
CHUNK: Final[int] = 960
def __init__(self, buffer: array.array[int]):
self.buffer = buffer
self._entered: bool = False
@property
def stream(self):
return self
def __enter__(self):
if self._entered:
log.warning('Already entered sr audio source')
self._entered = True
return self
def __exit__(self, *exc) -> None:
self._entered = False
if any(exc):
log.exception('Error closing sr audio source')
def read(self, size: int) -> bytes:
# TODO: make this timeout configurable
for _ in range(10):
if len(self.buffer) < size * self.CHANNELS:
time.sleep(0.1)
else:
break
else:
if len(self.buffer) == 0:
return b''
chunksize = size * self.CHANNELS
audiochunk = self.buffer[:chunksize].tobytes()
del self.buffer[: min(chunksize, len(audiochunk))]
audiochunk = audioop.tomono(audiochunk, 2, 1, 1)
return audiochunk
def close(self) -> None:
self.buffer.clear()
+62
View File
@@ -0,0 +1,62 @@
# Start by making sure the `assemblyai` package is installed.
# If not, you can install it by running the following command:
# pip install -U assemblyai
#
# Then, make sure you have PyAudio installed: https://pypi.org/project/PyAudio/
#
# Note: Some macOS users might need to use `pip3` instead of `pip`.
import pyaudio
import assemblyai as aai
aai.settings.api_key = "aa9962f0088a449a9c4ab2361e96cc08"
def on_open(session_opened: aai.RealtimeSessionOpened):
"This function is called when the connection has been established."
print("Session ID:", session_opened.session_id)
def on_data(transcript: aai.RealtimeTranscript):
"This function is called when a new transcript has been received."
if not transcript.text:
return
if isinstance(transcript, aai.RealtimeFinalTranscript):
print(transcript.text, end="\r\n")
else:
print(transcript.text, end="\r")
def on_error(error: aai.RealtimeError):
"This function is called when the connection has been closed."
print("An error occured:", error)
def on_close():
"This function is called when the connection has been closed."
print("Closing Session")
transcriber = aai.RealtimeTranscriber(
on_data=on_data,
on_error=on_error,
sample_rate=44_100,
on_open=on_open, # optional
on_close=on_close, # optional
)
pa = pyaudio.PyAudio()
for i in range(pa.get_device_count()):
print (pa.get_device_info_by_index(i))
# Start the connection
#transcriber.connect()
# Open a microphone stream
#microphone_stream = aai.extras.MicrophoneStream()
# Press CTRL+C to abort
#transcriber.stream(microphone_stream)
#transcriber.close()
+7
View File
@@ -0,0 +1,7 @@
import time
first_time = time.time_ns()
time.sleep(1)
time_diff = time.time_ns() - first_time
print(time_diff)
#2000149433
+47
View File
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
import discord
from discord.ext import commands, voice_recv
discord.opus._load_default()
bot = commands.Bot(command_prefix=commands.when_mentioned, intents=discord.Intents.all())
class Testing(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command()
async def test(self, ctx):
def callback(user, data: voice_recv.VoiceData):
print(f"Got packet from {user}")
## voice power level, how loud the user is speaking
# ext_data = packet.extension_data.get(voice_recv.ExtensionID.audio_power)
# value = int.from_bytes(ext_data, 'big')
# power = 127-(value & 127)
# print('#' * int(power * (79/128)))
## instead of 79 you can use shutil.get_terminal_size().columns-1
vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient)
vc.listen(voice_recv.BasicSink(callback))
@commands.command()
async def stop(self, ctx):
await ctx.voice_client.disconnect()
@commands.command()
async def die(self, ctx):
ctx.voice_client.stop()
await ctx.bot.close()
@bot.event
async def on_ready():
print('Logged in as {0.id}/{0}'.format(bot.user))
print('------')
await bot.add_cog(Testing(bot))
REMOTE_HOST_NAME = "discord"
bot.run("NzUxNDQxODgzNDA2MDA4NDEw.Go--wb.Nr28Oo4eYAP1p2XFVHF5uIQfcPD7s_jO2NfMCQ")
+233
View File
@@ -0,0 +1,233 @@
import logging
import wave
import asyncio
import assemblyai as aai
import discord
from discord.ext import commands, voice_recv, tasks
from discord.opus import Decoder as OpusDecoder
import time
# Replace with your API key
aai.settings.api_key = "aa9962f0088a449a9c4ab2361e96cc08"
discord.opus._load_default()
CHANNELS = OpusDecoder.CHANNELS
SAMPLE_WIDTH = OpusDecoder.SAMPLE_SIZE // OpusDecoder.CHANNELS
SAMPLING_RATE = OpusDecoder.SAMPLING_RATE
#rotate file after there is 0.5s between last received pcm for user.
#delete messsages after user disconnect
logger = logging.getLogger("discord")
location = "/home/pi/Conjurer/transcripts/"
class CommunicationObject:
def __init__(self, msg_type, user, data):
self.type = msg_type
self.user = user
self.data = data
def __repr__(self):
return f"{self.type} : {self.user} : {self.data}"
class WaveWriter:
def __init__(self, user_id, queue):
self.queue = queue
self.user = user_id
self.username = str(user_id.id)
#here we can add hashing function to make transcription files not possible to be connected with discord id
self.present_file_id = 0
self.file_name_past = None
self.file_name_present = location + self.username + "_" + str(self.present_file_id) + ".mp3"
self.transcript_file_present : wave.Wave_write = wave.open(
self.file_name_present, "wb"
)
self.transcript_file_present.setnchannels(CHANNELS)
self.transcript_file_present.setsampwidth(SAMPLE_WIDTH)
self.transcript_file_present.setframerate(SAMPLING_RATE)
self.file_name_future = location + self.username + "_" + str(self.present_file_id + 1) + ".mp3"
self.transcript_file_future : wave.Wave_write = wave.open(
self.file_name_future, "wb"
)
self.transcript_file_future.setnchannels(CHANNELS)
self.transcript_file_future.setsampwidth(SAMPLE_WIDTH)
self.transcript_file_future.setframerate(SAMPLING_RATE)
async def rotate(self):
self.transcript_file_present.close()
self.file_name_past = self.file_name_present
if self.present_file_id > 10:
self.present_file_id = 0
else:
self.present_file_id += 1
self.file_name_present = self.file_name_future
self.transcript_file_present = self.transcript_file_future
self.file_name_future = location + self.username + "_" + str(self.present_file_id + 1) + ".mp3"
self.transcript_file_future : wave.Wave_write = wave.open(
self.file_name_future, "wb"
)
self.transcript_file_future.setnchannels(CHANNELS)
self.transcript_file_future.setsampwidth(SAMPLE_WIDTH)
self.transcript_file_future.setframerate(SAMPLING_RATE)
operation = CommunicationObject(msg_type="send_file",user=self.user,data=[self.file_name_past])
await self.queue.put(operation)
def writeframes(self, pcmdata):
self.transcript_file_present.writeframes(pcmdata)
def cleanup(self):
logger.info("Cleanup for user %s", self.username)
self.transcript_file_present.close()
self.transcript_file_future.close()
class SRBuffer(voice_recv.AudioSink):
"""Endpoint AudioSink that generates a wav file.
Best used in conjunction with a silence generating sink. (TBD)
"""
# on member join dodajemytypa do listy
# on member disconnect - dropujemy go
def __init__(self, queue):
super().__init__()
self.queue = queue
self.wavewriter = {}
def on_user_connect(self, username):
self.wavewriter[str(username.id)] = [WaveWriter(username, self.queue), time.time_ns(), time.time_ns(), False, False]
def on_user_disconnect(self,username):
self.wavewriter[str(username.id)].cleanup()
self.wavewriter.pop(str(username.id))
def wants_opus(self) -> bool:
return False
def write(self, user, data) -> None:
#logger.info("DAta write")
if user:
self.wavewriter[str(user.id)][0].writeframes(data.pcm)
self.wavewriter[str(user.id)][1] = time.time_ns() #time from last write
self.wavewriter[str(user.id)][3] = True #data written in last iter
self.wavewriter[str(user.id)][4] = True #data in buff
time.sleep(0.001)
def cleanup(self) -> None:
try:
logger.info("Cleanup for SRBuffer")
for item in self.wavewriter.values():
item[0].cleanup()
except Exception:
logger.warning("WaveSink got error closing file on cleanup", exc_info=True)
class Transcriber(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.threads = []
self.comm_queue = asyncio.Queue()
self.wsink = SRBuffer(self.comm_queue)
self.worker = None
self.mc = None
self.vc = None
@commands.hybrid_command(name="transcribe")
async def test(self, ctx):
if self.vc:
vc = self.vc #to juz powinien byc voice channel z funkcja conenct
else:
vc = None
self.mc = ctx.message.channel
if self.bot.voice_clients:
if isinstance(self.bot.voice_clients[0], voice_recv.VoiceRecvClient):
logger.debug("Already transcribing")
else:
logger.debug("Already connected with other client")
else:
logger.debug("Connected")
vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient)
logger.info(self.bot.voice_clients)
self.check_data.start()
self.worker = asyncio.create_task(self.transcribe_output_queue())
vc.listen(self.wsink)
@tasks.loop(seconds=0.5)
async def check_data(self):
for item in self.wsink.wavewriter.values():
if not item[3]:
timediff_rotation = time.time_ns() - item[2]
timediff_write = time.time_ns() -item[1]
if timediff_rotation > 13000149433 and timediff_write > 500014943 and item[4]:
logger.info("File rotation time since last write %s %s", timediff_write, timediff_write/1e9)
logger.info("File rotation time since last rotation %s %s",timediff_rotation, timediff_rotation/1e9)
item[2] = time.time_ns()
item[4] = False
await item[0].rotate()
item[3] = False
@commands.Cog.listener()
async def on_voice_state_update(self, user, before, after):
if user == self.bot.user:
logger.debug("Ignoring self")
return
if before.channel is None:
logger.info("User %s connected to channel %s", user, after.channel.name)
self.wsink.on_user_connect(user)
elif after.channel is None:
logger.info("User %s disconnected from channel %s", user, before.channel.name)
operation = CommunicationObject(msg_type="user_cleanup", user=user, data=None)
await self.comm_queue.put(operation)
else:
logger.debug("User VC status changed %s", user.id)
logger.debug("Before %s", before)
logger.debug("After %s", after)
@commands.command(name="stop_transcribe")
async def stop(self, ctx):
self.check_data.stop()
stop_token = CommunicationObject("STOP",None,None)
await self.comm_queue.put(stop_token)
await ctx.voice_client.disconnect()
async def transcribe_output_queue(self):
logger.info("Transcript uploader start")
config = aai.TranscriptionConfig(language_code="pl")
transcriber = aai.Transcriber()
logger.debug("Transcriber queue id: %s",id(self.comm_queue))
while True:
logger.debug("waiting for tasks")
item = await self.comm_queue.get()
logger.debug("Got %s", item)
if "STOP" in item.type:
logger.debug("Queue ended")
break
elif "send_file" in item.type:
logger.debug("Sending file for transcription")
transcript = None
for iter in item.data:
try:
coro = asyncio.to_thread(
transcriber.transcribe,
iter,
config=config
)
transcript = await coro
except Exception as e:
logger.warn("Exceptiom occured %s", e)
await self.mc.send(f"{item.user} : {transcript.text}")
if transcript.error:
logger.error(transcript.error)
raise AssertionError
elif "user_cleanup" in item.type:
logger.info("User %s disconnected - cleanup action")
else:
logger.warn("Something went wrong with object %s", item)
logger.info("Transcript uploader stoped")
async def setup(bot):
logger = logging.getLogger("discord")
await bot.add_cog(Transcriber(bot))
logger.info("Loading voice transcribed module done")