From 9ef6dfdf98f38d96aeb3d4e94cd729576201cf4e Mon Sep 17 00:00:00 2001 From: nex Date: Wed, 7 Feb 2024 16:53:29 +0000 Subject: [PATCH] add opusinate --- src/cogs/ffmeta.py | 106 ++++++++++++++++++++++++++++++++++++++++++++- src/conf.py | 22 +++++++++- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/cogs/ffmeta.py b/src/cogs/ffmeta.py index 0e9a250..f43fbfa 100644 --- a/src/cogs/ffmeta.py +++ b/src/cogs/ffmeta.py @@ -1,6 +1,8 @@ import asyncio import io +import json import logging +import tempfile import typing import PIL.Image @@ -9,6 +11,7 @@ import discord import httpx from discord.ext import commands +from conf import VERSION class FFMeta(commands.Cog): @@ -94,7 +97,9 @@ class FFMeta(commands.Cog): await ctx.defer() src = io.BytesIO() - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient( + headers={"User-Agent": f"DiscordBot (Jimmy, v2, {VERSION}, +https://github.com/nexy7574/college-bot-v2)"} + ) as client: response = await client.get(url) if response.status_code != 200: return @@ -109,6 +114,105 @@ class FFMeta(commands.Cog): else: await ctx.respond(file=discord.File(dst, filename=f"jpegified.{image_format}")) + @commands.slash_command() + async def opusinate( + self, + ctx: discord.ApplicationContext, + url: str = None, + attachment: discord.Attachment = None, + bitrate: typing.Annotated[ + int, + discord.Option( + int, + description="The bitrate in kilobits of the resulting audio from 1-512", + default=64, + min_value=1, + max_value=512 + ) + ] = 64, + mono: typing.Annotated[ + bool, + discord.Option( + bool, + description="Whether to convert the audio to mono", + default=False + ) + ] = False + ): + """Converts a given URL or attachment to an Opus file""" + if url is None: + if attachment is None: + return await ctx.respond("No URL or attachment provided") + url = attachment.url + + await ctx.defer() + channels = 2 if not mono else 1 + with tempfile.NamedTemporaryFile() as temp: + async with httpx.AsyncClient( + headers={"User-Agent": f"DiscordBot (Jimmy, v2, {VERSION}, +https://github.com/nexy7574/college-bot-v2)"} + ) as client: + response = await client.get(url) + if response.status_code != 200: + return + temp.write(response.content) + temp.flush() + + probe_process = await asyncio.create_subprocess_exec( + "ffprobe", + "-v", "quiet", + "-print_format", "json", + "-show_format", + "-i", + temp.name, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await probe_process.communicate() + stdout = stdout.decode("utf-8", "replace") + data = {"format": {"duration": 195}} # 3 minutes and 15 seconds is the 2023 average. + if stdout: + try: + data = json.loads(stdout) + except json.JSONDecodeError: + pass + + duration = data["format"].get("duration", 195) + max_end_size = ((bitrate * duration * channels) / 8) * 1024 + if max_end_size > (24.75 * 1024 * 1024): + return await ctx.respond( + "The file would be too large to send ({:,.2f} MiB).".format(max_end_size / 1024 / 1024) + ) + + process = await asyncio.create_subprocess_exec( + "ffmpeg", + "-hide_banner", + "-i", + temp.name, + "-c:a", "libopus", + "-b:a", f"{bitrate}k", + "-vn", + "-sn", + "-ac", str(channels), + "-f", "opus", + "pipe:1", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await process.communicate() + stderr = stderr.decode("utf-8", "replace") + + paginator = commands.Paginator(prefix="```", suffix="```") + for line in stderr.splitlines(): + paginator.add_line(f"{line}"[:2000]) + + for page in paginator.pages: + await ctx.respond(page) + + file = io.BytesIO(stdout) + if (fs := len(file.getvalue())) > (24.75 * 1024 * 1024): + return await ctx.respond("The file is too large to send ({:,.2f} MiB).".format(fs / 1024 / 1024)) + await ctx.respond(file=discord.File(file, filename="opusinated.ogg")) + def setup(bot: commands.Bot): bot.add_cog(FFMeta(bot)) diff --git a/src/conf.py b/src/conf.py index 2fd7a74..7832493 100644 --- a/src/conf.py +++ b/src/conf.py @@ -1,7 +1,27 @@ import toml import logging +import subprocess from pathlib import Path +log = logging.getLogger("jimmy.autoconf") + +if (Path.cwd() / ".git").exists(): + try: + log.debug("Attempting to auto-detect running version using git.") + VERSION = subprocess.run( + ["git", "rev-parse", "--short", "HEAD"], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True, + check=True + ).stdout.strip() + except subprocess.CalledProcessError: + log.debug("Unable to auto-detect running version using git.", exc_info=True) + VERSION = "unknown" +else: + log.debug("Unable to auto-detect running version using git, no .git directory exists.") + VERSION = "unknown" + try: CONFIG = toml.load('config.toml') CONFIG.setdefault("logging", {}) @@ -25,5 +45,5 @@ try: ) except FileNotFoundError: cwd = Path.cwd() - logging.getLogger("jimmy.autoconf").critical("Unable to locate config.toml in %s.", cwd, exc_info=True) + log.critical("Unable to locate config.toml in %s.", cwd, exc_info=True) raise