# img_bot.py import discord, os, io, re, asyncio, logging, requests, replicate, subprocess from transformers import pipeline as transformers_pipeline # 번역 파이프라인 # ── 환경 변수 ──────────────────────────────────────────────── TOKEN = os.getenv("DISCORD_TOKEN") # Discord 봇 토큰 CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID")) # 감시할 채널 ID REPL_TOKEN = (os.getenv("OPENAI_API_KEY") or "").strip() # Replicate 토큰(동일 변수 사용) HF_TOKEN = (os.getenv("HF_TOKEN") or "").strip() # Hugging Face Personal Access Token if not TOKEN or not CHANNEL_ID: raise RuntimeError("DISCORD_TOKEN 과 DISCORD_CHANNEL_ID 환경 변수를 모두 지정하세요.") if not REPL_TOKEN: raise RuntimeError( "OPENAI_API_KEY 환경 변수에 Replicate Personal Access Token 값을 넣어주세요." ) # Replicate 라이브러리가 참조하도록 토큰 주입 os.environ["REPLICATE_API_TOKEN"] = REPL_TOKEN # ── 모델 ──────────────────────────────────────────────────── MODEL = "luma/ray-flash-2-540p" # 텍스트-투-비디오 모델 (540p) # ── 번역 파이프라인 (CPU) ─────────────────────────────────── translator_kwargs = {"device": -1} if HF_TOKEN: translator_kwargs["token"] = HF_TOKEN # 인증 토큰 전달 translator = transformers_pipeline( "translation", model="Helsinki-NLP/opus-mt-ko-en", **translator_kwargs ) def ko2en(text: str) -> str: """한글 포함 시 영어 번역, 그렇지 않으면 원문 반환.""" if re.search(r"[가-힣]", text): try: return translator(text, max_length=512)[0]["translation_text"].strip() except Exception as e: logging.warning(f"번역 실패, 원문 사용: {e}") return text # ── 로깅 ──────────────────────────────────────────────────── logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[logging.StreamHandler()] ) # ── Discord 설정 ──────────────────────────────────────────── intents = discord.Intents.default() intents.message_content = True # 메시지 콘텐츠 읽기 class ImageBot(discord.Client): # 이름 유지 async def on_ready(self): logging.info(f"Logged in as {self.user} (id={self.user.id})") # web.py 병렬 실행 try: subprocess.Popen(["python", "web.py"]) logging.info("web.py server has been started.") except Exception as e: logging.warning(f"web.py 실행 실패: {e}") async def on_message(self, message: discord.Message): # 봇 자신의 메시지 혹은 대상 아닌 채널 → 무시 if message.author.id == self.user.id or message.channel.id != CHANNEL_ID: return prompt = message.content.strip() if not prompt: return prompt_en = ko2en(prompt) # 한글이면 영어로 변환 await message.channel.typing() # ── Replicate 호출 ────────────────────────────────── def run_replicate(): # 필요한 경우 input 딕셔너리에 duration, seed 등 추가 가능 return replicate.run(MODEL, input={"prompt": prompt_en}) try: output = await asyncio.get_running_loop().run_in_executor(None, run_replicate) except Exception as e: logging.error(f"Replicate error: {e}") await message.reply("⚠️ 비디오 생성 실패!") return # ── 비디오 Discord 전송 ───────────────────────────── try: # replicate.run 결과가 list/tuple 이면 첫 번째 요소 사용 if isinstance(output, (list, tuple)): output = output[0] # URL(str) → 다운로드, bytes/file-like → 그대로 if isinstance(output, str): data = requests.get(output).content else: data = output.read() if hasattr(output, "read") else output video_file = discord.File(io.BytesIO(data), filename="output.mp4") await message.reply(files=[video_file]) except Exception as e: logging.warning(f"비디오 처리 실패: {e}") await message.reply("⚠️ 비디오를 전송할 수 없습니다.") # ── 실행 ──────────────────────────────────────────────────── if __name__ == "__main__": replicate.Client(api_token=REPL_TOKEN) # Replicate 인증 ImageBot(intents=intents).run(TOKEN)