Improve yt_dl

This commit is contained in:
Nexus 2023-04-28 21:31:00 +01:00
parent 58b406b572
commit e0a276c8ef
Signed by: nex
GPG key ID: 0FA334385D0B689F

View file

@ -6,6 +6,7 @@ import random
import re import re
import tempfile import tempfile
import textwrap import textwrap
import gzip
from datetime import timedelta from datetime import timedelta
from io import BytesIO from io import BytesIO
@ -23,7 +24,7 @@ from urllib.parse import urlparse
import aiohttp import aiohttp
import discord import discord
import psutil import psutil
from discord.ext import commands from discord.ext import commands, pages
from rich.tree import Tree from rich.tree import Tree
from selenium import webdriver from selenium import webdriver
from selenium.common.exceptions import WebDriverException from selenium.common.exceptions import WebDriverException
@ -35,18 +36,98 @@ from selenium.webdriver.firefox.service import Service as FirefoxService
from utils import console from utils import console
try:
from config import proxy
except ImportError:
proxy = None
try:
from config import proxies
except ImportError:
if proxy:
proxies = [proxy] * 2
else:
proxies = []
_engine = pyttsx3.init() _engine = pyttsx3.init()
# noinspection PyTypeChecker # noinspection PyTypeChecker
VOICES = [x.id for x in _engine.getProperty("voices")] VOICES = [x.id for x in _engine.getProperty("voices")]
del _engine del _engine
def format_autocomplete(ctx: discord.AutocompleteContext):
url = ctx.options.get("url", os.urandom(6).hex())
self: "OtherCog" = ctx.bot.cogs["OtherCog"] # type: ignore
if url in self._fmt_cache:
return [x for x in self._fmt_cache[url].keys() if ctx.value.lower() in x.lower()]
try:
parsed = urlparse(url, allow_fragments=True)
except ValueError:
pass
else:
if parsed.scheme in ("http", "https") and parsed.netloc:
self._fmt_queue.put_nowait(url)
return ["no formats cached (yet)."]
# noinspection DuplicatedCode # noinspection DuplicatedCode
class OtherCog(commands.Cog): class OtherCog(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
self.http = httpx.AsyncClient() self.http = httpx.AsyncClient()
self._fmt_cache = {}
self._fmt_queue = asyncio.Queue()
self._worker_task = self.bot.loop.create_task(self.cache_population_job())
def cog_unload(self):
self._worker_task.cancel()
async def cache_population_job(self):
while True:
url = await self._fmt_queue.get()
if url not in self._fmt_cache:
await self.list_formats(url, use_proxy=1)
self._fmt_queue.task_done()
async def list_formats(self, url: str, *, use_proxy: int = 0) -> dict:
if url in self._fmt_cache:
return self._fmt_cache[url]
args = [
"yt-dlp",
"-J",
url
]
if use_proxy == 1 and proxy:
args.append("--proxy")
args.append(proxy)
console.log("list_formats using proxy: %r" % args[-1])
elif use_proxy == 2 and proxies:
args.append("--proxy")
args.append(random.choice(proxies))
console.log("list_formats using random proxy: %r" % args[-1])
process = await asyncio.create_subprocess_exec(
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
data = json.loads(stdout.decode())
formats = data["formats"]
new = {}
for fmt in formats:
new[fmt["format_id"]] = {
"id": fmt["format_id"],
"ext": fmt["ext"],
"protocol": fmt["protocol"],
"acodec": fmt["acodec"],
"vcodec": fmt["vcodec"],
"resolution": fmt["resolution"],
"filesize": fmt.get("filesize", float('inf')),
}
self._fmt_cache[url] = new
return new
class AbortScreenshotTask(discord.ui.View): class AbortScreenshotTask(discord.ui.View):
def __init__(self, task: asyncio.Task): def __init__(self, task: asyncio.Task):
@ -748,37 +829,116 @@ class OtherCog(commands.Cog):
self, self,
ctx: discord.ApplicationContext, ctx: discord.ApplicationContext,
url: str, url: str,
video_format: str = "", video_format: discord.Option(
upload_log: bool = True description="The format to download the video in.",
autocomplete=format_autocomplete,
default=""
) = "",
upload_log: bool = True,
list_formats: bool = False,
proxy_mode: discord.Option(
str,
choices=[
"No Proxy",
"Dedicated Proxy",
"Random Public Proxy"
],
description="Only use if a download was blocked or 403'd.",
default="No Proxy",
) = "No Proxy",
): ):
"""Downloads a video from <URL> using youtube-dl""" """Downloads a video from <URL> using youtube-dl"""
use_proxy = ["No Proxy", "Dedicated Proxy", "Random Public Proxy"].index(proxy_mode)
embed = discord.Embed(
description="Loading..."
)
embed.set_thumbnail(url="https://cdn.discordapp.com/emojis/1101463077586735174.gif?v=1")
await ctx.defer() await ctx.defer()
await ctx.respond(embed=embed)
if list_formats:
# Nothing actually downloads here
try:
formats = await self.list_formats(url, use_proxy=use_proxy)
except FileNotFoundError:
_embed = embed.copy()
_embed.description = "yt-dlp not found."
_embed.colour = discord.Colour.red()
_embed.set_thumbnail(url=discord.Embed.Empty)
return await ctx.edit(embed=_embed)
except json.JSONDecodeError:
_embed = embed.copy()
_embed.description = "Unable to find formats. You're on your own. Wing it."
_embed.colour = discord.Colour.red()
_embed.set_thumbnail(url=discord.Embed.Empty)
return await ctx.edit(embed=_embed)
else:
embeds = []
for fmt in formats.keys():
fs = formats[fmt]["filesize"] or 0.1
if fs == float("inf"):
fs = 0
units = ["B"]
else:
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
while fs > 1024:
fs /= 1024
units.pop(0)
embeds.append(
discord.Embed(
title=fmt,
description="- Encoding: {0[vcodec]} + {0[acodec]}\n"
"- Extension: `.{0[ext]}`\n"
"- Resolution: {0[resolution]}\n"
"- Filesize: {1}\n"
"- Protocol: {0[protocol]}\n".format(formats[fmt], f"{round(fs, 2)}{units[0]}"),
colour=discord.Colour.blurple()
).add_field(
name="Download:",
value="{} url:{} video_format:{}".format(
self.bot.get_application_command("yt-dl").mention,
url,
fmt
)
)
)
_paginator = pages.Paginator(embeds, loop_pages=True)
await ctx.delete(delay=0.1)
return await _paginator.respond(ctx.interaction)
with tempfile.TemporaryDirectory(prefix="jimmy-ytdl-") as tempdir: with tempfile.TemporaryDirectory(prefix="jimmy-ytdl-") as tempdir:
video_format = video_format.lower() video_format = video_format.lower()
OUTPUT_FILE = str(Path(tempdir) / f"{ctx.user.id}.%(ext)s")
MAX_SIZE = round(ctx.guild.filesize_limit / 1024 / 1024) MAX_SIZE = round(ctx.guild.filesize_limit / 1024 / 1024)
if MAX_SIZE == 8:
MAX_SIZE = 25
options = [ options = [
"--no-colors", "--no-colors",
"--no-playlist", "--no-playlist",
"--no-check-certificates", "--no-check-certificates",
# "--max-filesize", str(MAX_SIZE) + "M",
"--no-warnings", "--no-warnings",
"--output", OUTPUT_FILE, "--newline",
"--newline" "--output",
f"{ctx.user.id}.%(title)s.%(ext)s",
] ]
if video_format: if video_format:
options.extend(["--format", f"({video_format})[filesize<={MAX_SIZE}M]"]) options.extend(["--format", f"({video_format})[filesize<={MAX_SIZE}M]"])
else: else:
options.extend(["--format", f"(bv*+ba/b/ba)[filesize<={MAX_SIZE}M]"]) options.extend(["--format", f"(bv*+ba/b/ba)[filesize<={MAX_SIZE}M]"])
if use_proxy == 1 and proxy:
options.append("--proxy")
options.append(proxy)
console.log("yt-dlp using proxy: %r", proxy)
elif use_proxy == 2 and proxies:
options.append("--proxy")
options.append(random.choice(proxies))
console.log("yt-dlp using random proxy: %r", options[-1])
_embed = embed.copy()
_embed.description = "Downloading..."
_embed.colour = discord.Colour.blurple()
await ctx.edit( await ctx.edit(
embed=discord.Embed( embed=_embed,
description="\u200b"
).set_author(
name="Downloading...",
icon_url="https://cdn.discordapp.com/emojis/1101463077586735174.gif?v=1",
url=url
)
) )
try: try:
venv = Path.cwd() / "venv" / ("Scripts" if os.name == "nt" else "bin") venv = Path.cwd() / "venv" / ("Scripts" if os.name == "nt" else "bin")
@ -815,51 +975,56 @@ class OtherCog(commands.Cog):
stderr_log_file stderr_log_file
] ]
if b"format is not available" in stderr: if b"format is not available" in stderr:
process = await asyncio.create_subprocess_exec( formats = await self.list_formats(url)
"yt-dlp",
"-J",
url,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
data = json.loads(stdout.decode())
formats = data["formats"]
paginator = commands.Paginator()
for fmt in formats:
fs = round((fmt.get("filesize") or len(fmt.get("fragments", [b'\0'])) * 10) / 1024 / 1024, 1)
paginator.add_line(
"* {0[format_id]}:\n"
"\t- Encoding: {0[vcodec]} + {0[acodec]}\n"
"\t- Extension: {0[ext]}\n"
"\t- Protocol: {0[protocol]}\n"
"\t- Resolution: {0[resolution]}\n"
"\t- Size: {1!s}MB".format(fmt, fs)
)
await ctx.edit(content="Invalid format. Available formats:", embed=None)
for page in paginator.pages:
await ctx.send(page)
return await ctx.send(files=files)
return await ctx.edit(content=f"Download failed:\n```\n{stderr.decode()}\n```", files=files) return await ctx.edit(content=f"Download failed:\n```\n{stderr.decode()}\n```", files=files)
await ctx.edit(content="Uploading video...") _embed = embed.copy()
_embed.description = "Download complete."
_embed.colour = discord.Colour.green()
_embed.set_thumbnail(url=discord.Embed.Empty)
await ctx.edit(embed=_embed)
files = [ files = [
stdout_log_file, stdout_log_file,
stderr_log_file stderr_log_file
] if upload_log else [] ] if upload_log else []
cum_size = 0
for file in files:
n_b = len(file.fp.read())
file.fp.seek(0)
if n_b == 0:
files.remove(file)
continue
elif n_b >= 1024 * 1024 * 256:
data = file.fp.read()
compressed = await self.bot.loop.run_in_executor(
gzip.compress, data, 9
)
file.fp.close()
file.fp = io.BytesIO(compressed)
file.fp.seek(0)
file.filename += ".gz"
cum_size += len(compressed)
else:
cum_size += n_b
for file_name in Path(tempdir).glob(f"{ctx.user.id}.*"): for file_name in Path(tempdir).glob(f"{ctx.user.id}.*"):
stat = file_name.stat() stat = file_name.stat()
size_mb = stat.st_size / 1024 / 1024 size_mb = stat.st_size / 1024 / 1024
if size_mb > MAX_SIZE - 0.5: if (size_mb * 1024 * 1024 + cum_size) >= (MAX_SIZE - 0.256) * 1024 * 1024:
warning = f"File {file_name.name} was too large ({size_mb:,.1f}MB vs {MAX_SIZE:.1f}MB)".encode()
_x = io.BytesIO( _x = io.BytesIO(
f"File {file_name.name} was too large ({size_mb:,.1f}MB vs {MAX_SIZE:.1f}MB)".encode() warning
) )
_x.seek(0)
cum_size += len(warning)
files.append(discord.File(_x, filename=file_name.name + ".txt")) files.append(discord.File(_x, filename=file_name.name + ".txt"))
try: try:
video = discord.File(file_name, filename=file_name.name) video = discord.File(file_name, filename=file_name.name)
files.append(video) files.append(video)
except FileNotFoundError: except FileNotFoundError:
continue continue
else:
cum_size += size_mb * 1024 * 1024
if not files: if not files:
return await ctx.edit(embed=discord.Embed(description="No files found.", color=discord.Colour.red())) return await ctx.edit(embed=discord.Embed(description="No files found.", color=discord.Colour.red()))
@ -1009,6 +1174,7 @@ class OtherCog(commands.Cog):
async def quote(self, ctx: discord.ApplicationContext): async def quote(self, ctx: discord.ApplicationContext):
"""Generates a random quote""" """Generates a random quote"""
emoji = discord.PartialEmoji(name='loading', animated=True, id=1101463077586735174) emoji = discord.PartialEmoji(name='loading', animated=True, id=1101463077586735174)
async def get_quote() -> str | discord.File: async def get_quote() -> str | discord.File:
try: try:
response = await self.http.get("https://inspirobot.me/api?generate=true") response = await self.http.get("https://inspirobot.me/api?generate=true")