sourcecode6687/Gupshup
0
1import asyncio2import os3import socket4import dns.resolver5import yt_dlp6import logging7import random8import nest_asyncio9 10# Apply nest_asyncio to prevent event loop issues11nest_asyncio.apply()12 13# Logger14logging.basicConfig(level=logging.INFO)15logger = logging.getLogger("AudioEngine")16 17# --- 0. PREPARE RESOLVER ---18resolver = dns.resolver.Resolver()19resolver.nameservers = ['8.8.8.8', '8.8.4.4']20_original_getaddrinfo = socket.getaddrinfo21 22# --- 1. NETWORK PATCH: FORCE IPv4 ---23def patched_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):24 try:25 answers = resolver.resolve(host, 'A') 26 ip = answers[0].to_text()27 return _original_getaddrinfo(ip, port, socket.AF_INET, type, proto, flags)28 except Exception:29 try:30 res = _original_getaddrinfo(host, port, family, type, proto, flags)31 return [r for r in res if r[0] == socket.AF_INET]32 except Exception:33 return []34 35socket.getaddrinfo = patched_getaddrinfo36 37class AudioEngine:38 def __init__(self, icecast_url):39 self.icecast_url = icecast_url40 self.process = None41 42 def get_ytdlp_opts(self):43 """44 ANDROID CLIENT CONFIGURATION45 Using 'android' bypasses the 'n-sig' JS challenges that fail on servers.46 """47 if not os.path.exists('cookies.txt'):48 logger.error("โ CRITICAL: cookies.txt not found!")49 50 opts = {51 'format': 'bestaudio/best',52 'noplaylist': True,53 'quiet': True,54 'force_ipv4': True,55 'source_address': '0.0.0.0',56 'cookiefile': 'cookies.txt', 57 58 # --- BYPASS CONFIGURATION ---59 'nocheckcertificate': True,60 'extractor_args': {61 'youtube': {62 # PRIORITIZE ANDROID to fix "Signature solving failed"63 'player_client': ['android', 'web'], 64 'skip': ['ios'] 65 }66 }67 }68 return opts69 70 async def get_stream_url(self, query):71 loop = asyncio.get_event_loop()72 try:73 with yt_dlp.YoutubeDL(self.get_ytdlp_opts()) as ydl:74 if not query.startswith("http"):75 search_query = f"ytsearch1:{query}"76 else:77 search_query = query78 79 logger.info(f"๐ Searching: {query}")80 info = await loop.run_in_executor(None, lambda: ydl.extract_info(search_query, download=False))81 82 if 'entries' in info:83 info = info['entries'][0]84 85 if not info or 'url' not in info:86 return None87 88 logger.info(f"โ
Found: {info.get('title')}")89 return {90 'url': info['url'], 91 'title': info.get('title', 'Unknown'),92 'duration': info.get('duration', 0)93 }94 except Exception as e:95 logger.error(f"โ ๏ธ YTDLP Error: {e}")96 return None97 98 async def stream(self, input_url, is_silence=False):99 self.stop() 100 command = ['ffmpeg', '-re', '-4', '-y'] 101 102 if is_silence:103 command.extend(['-f', 'lavfi', '-i', 'anullsrc=r=44100:cl=stereo'])104 else:105 command.extend([106 '-headers', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)',107 '-i', input_url108 ])109 110 command.extend([111 '-c:a', 'libmp3lame', '-b:a', '128k', '-ar', '44100', '-ac', '2',112 '-content_type', 'audio/mpeg', '-f', 'mp3',113 self.icecast_url114 ])115 116 try:117 self.process = await asyncio.create_subprocess_exec(118 *command,119 stdout=asyncio.subprocess.DEVNULL,120 stderr=asyncio.subprocess.DEVNULL121 )122 await self.process.wait()123 except Exception as e:124 logger.error(f"Stream Error: {e}")125 126 def stop(self):127 if self.process:128 try: self.process.terminate()129 except: pass130 