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!")