Spaces:
Paused
Paused
| import shlex | |
| import subprocess | |
| from discord.opus import Encoder as OpusEncoder | |
| import logging | |
| import discord | |
| from yt_dlp import YoutubeDL | |
| import asyncio | |
| import re | |
| from nicodl import NicoNico as niconico_dl#ニコニコ関連 | |
| log = logging.getLogger(__name__) | |
| class OriginalFFmpegPCMAudio(discord.FFmpegPCMAudio): | |
| def __init__(self, | |
| source, | |
| *, | |
| executable='ffmpeg', | |
| pipe=False, | |
| stderr=None, | |
| before_options=None, | |
| options=None): | |
| self.total_milliseconds = 0 | |
| self.source = source | |
| super().__init__(source, | |
| executable=executable, | |
| pipe=pipe, | |
| stderr=stderr, | |
| before_options=before_options, | |
| options=options) | |
| def wait_buffer(self): | |
| self._stdout.peek(OpusEncoder.FRAME_SIZE) | |
| def read(self): | |
| ret = super().read() | |
| if ret: | |
| self.total_milliseconds += 20 | |
| return ret | |
| def get_tootal_millisecond(self, seek_time): | |
| if seek_time: | |
| list = reversed([int(x) for x in seek_time.split(":")]) | |
| total = 0 | |
| for i, x in enumerate(list): | |
| total += x * 3600 if i == 2 else x * 60 if i == 1 else x | |
| return max(1000 * total, 0) | |
| else: | |
| raise Exception() | |
| def rewind(self, | |
| rewind_time, | |
| *, | |
| executable='ffmpeg', | |
| pipe=False, | |
| stderr=None, | |
| before_options=None, | |
| options=None): | |
| seek_time = str( | |
| int((self.total_milliseconds - | |
| self.get_tootal_millisecond(rewind_time)) / 1000)) | |
| self.seek(seek_time=seek_time, | |
| executable=executable, | |
| pipe=pipe, | |
| stderr=stderr, | |
| before_options=before_options, | |
| options=options) | |
| def seek(self, | |
| seek_time, | |
| *, | |
| executable='ffmpeg', | |
| pipe=False, | |
| stderr=None, | |
| before_options=None, | |
| options=None): | |
| self.total_milliseconds = self.get_tootal_millisecond(seek_time) | |
| proc = self._process | |
| before_options = f"-ss {seek_time} " + before_options | |
| args = [] | |
| subprocess_kwargs = { | |
| 'stdin': self.source if pipe else subprocess.DEVNULL, | |
| 'stderr': stderr | |
| } | |
| if isinstance(before_options, str): | |
| args.extend(shlex.split(before_options)) | |
| args.append('-i') | |
| args.append('-' if pipe else self.source) | |
| args.extend(('-f', 's16le', '-ar', '48000', '-ac', '2', '-loglevel', | |
| 'warning')) | |
| if isinstance(options, str): | |
| args.extend(shlex.split(options)) | |
| args.append('pipe:1') | |
| args = [executable, *args] | |
| kwargs = {'stdout': subprocess.PIPE} | |
| kwargs.update(subprocess_kwargs) | |
| self._process = self._spawn_process(args, **kwargs) | |
| self._stdout = self._process.stdout | |
| self.kill(proc) | |
| def kill(self, proc): | |
| if proc is None: | |
| return | |
| log.info('Preparing to terminate ffmpeg process %s.', proc.pid) | |
| try: | |
| proc.kill() | |
| except Exception: | |
| log.exception( | |
| "Ignoring error attempting to kill ffmpeg process %s", | |
| proc.pid) | |
| if proc.poll() is None: | |
| log.info( | |
| 'ffmpeg process %s has not terminated. Waiting to terminate...', | |
| proc.pid) | |
| proc.communicate() | |
| log.info( | |
| 'ffmpeg process %s should have terminated with a return code of %s.', | |
| proc.pid, proc.returncode) | |
| else: | |
| log.info( | |
| 'ffmpeg process %s successfully terminated with return code of %s.', | |
| proc.pid, proc.returncode) | |
| ytdl_format_options = {'format': 'bestaudio/best','outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s','restrictfilenames': True,'noplaylist': True,'nocheckcertificate': True,'ignoreerrors': False,'logtostderr': False,'quiet': True,'no_warnings': True,'default_search': 'auto','source_address':'0.0.0.0','user-agent':"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0"} | |
| ffmpeg_options = {'before_options':'-vn -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5'} | |
| ytdlp = YoutubeDL(ytdl_format_options) | |
| class YTDLSource(discord.PCMVolumeTransformer): | |
| def __init__(self, source, *, data, volume=0.1): | |
| super().__init__(source, volume) | |
| self.data = data | |
| self.title = data.get('title') | |
| self.url = data.get('url') | |
| async def from_url(cls, url, *, loop=None, stream=False, volume=0.1): | |
| loop = loop or asyncio.get_event_loop() | |
| data = await loop.run_in_executor( | |
| None, lambda: ytdlp.extract_info(url, download=not stream)) | |
| if 'entries' in data: | |
| data = data['entries'][0] | |
| filename = data['url'] if stream else ytdlp.prepare_filename(data) | |
| source = OriginalFFmpegPCMAudio(filename, **ffmpeg_options) | |
| return cls(source, data=data, volume=volume) | |
| niconico_headers = { | |
| "Accept-Encoding": "gzip, deflate, br", | |
| "Accept-Language": "ja", | |
| "Connection": "keep-alive", | |
| "Host": "nvapi.nicovideo.jp", | |
| "Origin": "https://www.nicovideo.jp", | |
| "Referer": "https://www.nicovideo.jp/", | |
| "sec-ch-ua-mobile": "?0", | |
| "Sec-Fetch-Dest": "empty", | |
| "Sec-Fetch-Mode": "cors", | |
| "Sec-Fetch-Site": "same-site", | |
| "User-Agent": | |
| "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", | |
| "X-Frontend-Id": "6", | |
| "X-Frontend-Version": "0", | |
| "X-Niconico-Language": "ja-jp" | |
| } | |
| headers = { | |
| "User-Agent": | |
| "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0", | |
| } | |
| class NicoNicoDLSource(discord.PCMVolumeTransformer): | |
| def __init__(self, source, *, url, volume=0.1): | |
| super().__init__(source, volume) | |
| self.url = url | |
| async def from_url(cls, url, *, log=False, volume=0.1): | |
| nico_id = url.split("/")[-1] | |
| niconico = niconico_dl(nico_id, log=log) | |
| stream_url = await niconico.get_download_link() | |
| source = OriginalFFmpegPCMAudio(stream_url, **ffmpeg_options) | |
| return (cls(source, url=stream_url, volume=volume), niconico) |