diff --git a/Dockerfile b/Dockerfile index b4f3a60..c10b285 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,7 +66,8 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ python3 \ python3-pip \ python3-dev \ - python3-virtualenv + python3-virtualenv \ + libmagic-dev RUN virtualenv /app/venv RUN /app/venv/bin/pip install --upgrade --no-input pip wheel setuptools diff --git a/requirements.txt b/requirements.txt index ea5ac2c..eb92f63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ redis~=5.0 beautifulsoup4~=4.12 lxml~=5.1 matplotlib~=3.8 +python-magic~=0.4 diff --git a/src/cogs/ytdl.py b/src/cogs/ytdl.py index 23d23c9..af8d7de 100644 --- a/src/cogs/ytdl.py +++ b/src/cogs/ytdl.py @@ -2,10 +2,12 @@ import asyncio import functools import hashlib import logging +import httpx import subprocess import tempfile import textwrap import typing +import uuid from pathlib import Path from urllib.parse import urlparse @@ -205,6 +207,22 @@ class YTDLCog(commands.Cog): raise RuntimeError(process.stderr.decode()) return new_file + @staticmethod + async def upload_to_0x0(name: str, data: typing.BinaryIO[bytes], mime_type: str | None = None) -> str: + if not mime_type: + import magic + mime_type = await asyncio.to_thread(magic.from_buffer, data.read(4096), mime=True) + data.seek(0) + async with httpx.AsyncClient() as client: + response = await client.post( + "https://0x0.st", + files={"file": (name, data, mime_type)}, + headers={"User-Agent": "CollegeBot (matrix: @nex:nexy7574.co.uk)"}, + ) + if response.status_code == 200: + return urlparse(response.text).path[1:] + response.raise_for_status() + @commands.slash_command(name="yt-dl") @commands.max_concurrency(1, wait=False) # @commands.bot_has_permissions(send_messages=True, embed_links=True, attach_files=True) @@ -294,6 +312,7 @@ class YTDLCog(commands.Cog): description = str(e) thumbnail_url = webpage_url = None likes = views = 0 + chosen_format_id = str(uuid.uuid4()) else: title = extracted_info.get("title", url) title = textwrap.shorten(title, 100) @@ -305,7 +324,7 @@ class YTDLCog(commands.Cog): final_extension = extracted_info.get("ext") format_note = extracted_info.get("format_note", "%s (%s)" % (chosen_format, chosen_format_id)) resolution = extracted_info.get("resolution") - fps = extracted_info.get("fps") + fps = extracted_info.get("fps", 0.0) vcodec = extracted_info.get("vcodec") acodec = extracted_info.get("acodec") filesize = extracted_info.get("filesize", extracted_info.get("filesize_approx", 1)) @@ -346,7 +365,7 @@ class YTDLCog(commands.Cog): .set_footer(text="Downloading (step 2/10)") .set_thumbnail(url=thumbnail_url) ) - previous = await self.get_saved(webpage_url, extracted_info["format_id"], snip or "*") + previous = await self.get_saved(webpage_url, chosen_format_id, snip or "*") if previous: await ctx.edit( content=previous, @@ -375,26 +394,28 @@ class YTDLCog(commands.Cog): ) try: if audio_only is False: - file = next(temp_dir.glob("*." + extracted_info["ext"])) + file: Path = next(temp_dir.glob("*." + extracted_info["ext"])) else: # can be .opus, .m4a, .mp3, .ogg, .oga for _file in temp_dir.iterdir(): if _file.suffix in (".opus", ".m4a", ".mp3", ".ogg", ".oga", ".aac", ".wav"): - file = _file + file: Path = _file break else: raise StopIteration except StopIteration: + ext = extracted_info["ext"] self.log.warning( "Failed to locate downloaded file. Was supposed to be looking for a file extension of " "%r amongst files %r, however none were found.", - extracted_info["ext"], + ext, list(map(str, temp_dir.iterdir())), ) return await ctx.edit( embed=discord.Embed( title="Error", - description="Failed to locate downloaded video file.\n" + description="Failed to locate downloaded video file." + f" Was expecting a file with the extension {ext}.\n" f"Files: {', '.join(list(map(str, temp_dir.iterdir())))}", colour=discord.Colour.red(), url=webpage_url, @@ -421,7 +442,7 @@ class YTDLCog(commands.Cog): "-preset", "fast", "-crf", - "28", + "24", "-deadline", "realtime", "-cpu-used", @@ -464,11 +485,11 @@ class YTDLCog(commands.Cog): if audio_only and file.suffix != ".m4a": self.log.info("Converting %r to m4a.", file) - file = await asyncio.to_thread(self.convert_to_m4a, file) + file: Path = await asyncio.to_thread(self.convert_to_m4a, file) stat = file.stat() size_bytes = stat.st_size - if size_bytes >= ((25 * 1024 * 1024) - 256): + if size_bytes >= ((500 * 1024 * 1024) - 256): return await ctx.edit( embed=discord.Embed( title="Error", @@ -477,9 +498,9 @@ class YTDLCog(commands.Cog): url=webpage_url, ) ) + size_megabits = (size_bytes * 8) / 1024 / 1024 eta_seconds = size_megabits / 20 - upload_file = await asyncio.to_thread(discord.File, file, filename=file.name) await ctx.edit( embed=discord.Embed( title="Uploading...", @@ -488,19 +509,32 @@ class YTDLCog(commands.Cog): timestamp=discord.utils.utcnow(), ) ) + embed = discord.Embed( + title=f"Downloaded {title}!", + description="Views: {:,} | Likes: {:,}".format(views or 0, likes or 0), + colour=discord.Colour.green(), + timestamp=discord.utils.utcnow(), + url=webpage_url, + ) try: - msg = await ctx.edit( - file=upload_file, - embed=discord.Embed( - title=f"Downloaded {title}!", - description="Views: {:,} | Likes: {:,}".format(views or 0, likes or 0), - colour=discord.Colour.green(), - timestamp=discord.utils.utcnow(), - url=webpage_url, - ), - ) - await self.save_link(msg, webpage_url, chosen_format_id, snip=snip or "*") - except discord.HTTPException as e: + if size_bytes >= (20 * 1024 * 1024): + with file.open("rb") as fb: + part = await self.upload_to_0x0( + file.name, + fb + ) + await ctx.edit( + content="https://embeds.video/0x0/" + part, + embed=embed + ) + else: + upload_file = await asyncio.to_thread(discord.File, file, filename=file.name) + msg = await ctx.edit( + file=upload_file, + embed=embed + ) + await self.save_link(msg, webpage_url, chosen_format_id, snip=snip or "*") + except (discord.HTTPException, ConnectionError, httpx.HTTPStatusError) as e: self.log.error(e, exc_info=True) return await ctx.edit( embed=discord.Embed(