Music-discord-bot / ffmpeg.py
katasou's picture
Upload 2 files
7435029
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')
@classmethod
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
@classmethod
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)