Files
maciej-bot-2.0/voice_bot_live.py
2026-02-05 00:10:08 +01:00

171 lines
5.0 KiB
Python

import discord
from discord.ext import tasks, commands
from faster_whisper import WhisperModel
import os
import asyncio
import wave
import time
import ctypes.util
# ============================
# 0. FIX NA OPUS (DLA PEWNOŚCI)
# ============================
# Próba załadowania biblioteki systemowej, żeby uciszyć część błędów
opus_path = ctypes.util.find_library('opus')
if opus_path:
try:
discord.opus.load_opus(opus_path)
except:
pass
# ============================
# 1. KONFIGURACJA MODELU
# ============================
print("⏳ Ładowanie Faster-Whisper...")
model = WhisperModel("base", device="cpu", compute_type="int8")
print("✅ Model gotowy!")
# ============================
# 2. KLASA ODBIORNIKA
# ============================
class LiveSink(discord.sinks.Sink):
def __init__(self):
super().__init__()
self.user_buffers = {}
self.last_process_time = {}
# Fix dla biblioteki py-cord (obsługa startu)
def init(self, vc):
self.vc = vc
try:
super().init(vc)
except TypeError:
pass
# Fix dla biblioteki py-cord (obsługa zapisu danych)
@discord.sinks.Filters.container
def write(self, data, user):
if user not in self.user_buffers:
self.user_buffers[user] = bytearray()
self.last_process_time[user] = time.time()
self.user_buffers[user] += data
def get_audio_chunk(self, user):
if user in self.user_buffers:
data = self.user_buffers[user]
self.user_buffers[user] = bytearray() # Reset bufora
self.last_process_time[user] = time.time()
return data
return None
# ============================
# 3. LOGIKA PRZETWARZANIA
# ============================
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
current_sink = None
async def transcode_and_transcribe(user_id, pcm_data):
# Unikalna nazwa pliku
temp_filename = f"live_{user_id}_{int(time.time()*1000)}.wav"
try:
# Zapis PCM do WAV (format wymagany przez Whisper)
with wave.open(temp_filename, 'wb') as wav_file:
wav_file.setnchannels(2)
wav_file.setsampwidth(2)
wav_file.setframerate(48000)
wav_file.writeframes(pcm_data)
# Uruchomienie modelu w tle
loop = asyncio.get_event_loop()
def run_whisper():
segments, _ = model.transcribe(temp_filename, language="pl", beam_size=1)
return " ".join([s.text for s in segments])
text = await loop.run_in_executor(None, run_whisper)
# Filtrujemy puste wiadomości i halucynacje "dzięki"
if text.strip() and "dzięki" not in text.lower():
user = await bot.fetch_user(user_id)
print(f"🔴 LIVE {user.name}: {text}")
except Exception as e:
print(f"Błąd transkrypcji: {e}")
finally:
if os.path.exists(temp_filename):
os.remove(temp_filename)
@tasks.loop(seconds=3.0)
async def live_transcription_loop():
if current_sink is None:
return
try:
# Iterujemy po liście userów
user_ids = list(current_sink.user_buffers.keys())
for user_id in user_ids:
# Sprawdzamy czy user coś mówił (czy bufor > 100KB)
if len(current_sink.user_buffers[user_id]) > 100000:
pcm_data = current_sink.get_audio_chunk(user_id)
if pcm_data:
asyncio.create_task(transcode_and_transcribe(user_id, pcm_data))
except Exception:
pass
# --- TO JEST NOWA FUNKCJA NAPRAWIAJĄCA BŁĄD ---
async def dummy_callback(sink, *args):
# Ta funkcja musi być async, inaczej bot wywala błąd "coroutine required"
return
# ============================
# 4. KOMENDY
# ============================
@bot.event
async def on_ready():
print(f'🚀 Bot Live gotowy: {bot.user}')
@bot.command()
async def join(ctx):
if ctx.author.voice:
await ctx.author.voice.channel.connect()
await ctx.send("Dołączono.")
else:
await ctx.send("Wejdź na kanał.")
@bot.command()
async def start(ctx):
global current_sink
if not ctx.voice_client:
return await ctx.send("Nie jestem połączony.")
print("Rozpoczynam LIVE transkrypcję...")
current_sink = LiveSink()
# Tu była lambda, która psuła bota. Teraz jest dummy_callback.
ctx.voice_client.start_recording(
current_sink,
dummy_callback,
ctx.channel
)
live_transcription_loop.start()
await ctx.send("Nasłuchuję w trybie LIVE...")
@bot.command()
async def stop(ctx):
global current_sink
if ctx.voice_client:
ctx.voice_client.stop_recording() # To wywoła dummy_callback
live_transcription_loop.stop()
current_sink = None
await ctx.send("Zatrzymano.")
token = os.getenv("DISCORD_TOKEN")
if token:
bot.run(token)
else:
print("Brak tokena!")