2024-01-02 22:29:25 +00:00
|
|
|
import asyncio
|
|
|
|
import functools
|
2024-01-09 09:34:55 +00:00
|
|
|
import hashlib
|
2024-01-02 22:29:25 +00:00
|
|
|
import logging
|
2024-05-31 03:03:22 +01:00
|
|
|
import math
|
|
|
|
import time
|
|
|
|
|
2024-05-31 02:23:42 +01:00
|
|
|
import httpx
|
2024-04-18 00:24:58 +01:00
|
|
|
import subprocess
|
2024-01-02 22:59:47 +00:00
|
|
|
import tempfile
|
2024-01-02 22:29:25 +00:00
|
|
|
import textwrap
|
|
|
|
import typing
|
2024-05-31 02:23:42 +01:00
|
|
|
import uuid
|
2024-01-02 22:59:47 +00:00
|
|
|
from pathlib import Path
|
|
|
|
from urllib.parse import urlparse
|
2024-01-02 22:29:25 +00:00
|
|
|
|
2024-01-09 09:34:55 +00:00
|
|
|
import aiosqlite
|
2024-04-18 00:24:58 +01:00
|
|
|
import discord
|
2024-01-02 22:29:25 +00:00
|
|
|
import yt_dlp
|
|
|
|
from discord.ext import commands
|
|
|
|
|
|
|
|
COOKIES_TXT = Path.cwd() / "cookies.txt"
|
|
|
|
|
|
|
|
|
|
|
|
class YTDLCog(commands.Cog):
|
|
|
|
def __init__(self, bot: commands.Bot) -> None:
|
|
|
|
self.bot = bot
|
|
|
|
self.log = logging.getLogger("jimmy.cogs.ytdl")
|
|
|
|
self.common_formats = {
|
2024-05-31 02:28:56 +01:00
|
|
|
"144p": "bv[width<=144]+ba[ext=webm]/bv[width<=144]+ba[ext=m4a]/bv[width<=144]+ba/b[width<=144]",
|
|
|
|
"240p": "bv[width<=240]+ba[ext=webm]/bv[width<=240]+ba[ext=m4a]/bv[width<=240]+ba/b[width<=240]",
|
|
|
|
"360p": "bv[width<=360]+ba[ext=webm]/bv[width<=360]+ba[ext=m4a]/bv[width<=360]+ba/b[width<=360]",
|
|
|
|
"480p": "bv[width<=500]+ba[ext=webm]/bv[width<=500]+ba[ext=m4a]/bv[width<=500]+bab[width<=480]",
|
|
|
|
"720p": "bv[width<=720]+ba[ext=webm]/bv[width<=720]+ba[ext=m4a]/bv[width<=720]+ba/b[width<=720]",
|
|
|
|
"1080p": "bv[width<=1080]+ba[ext=webm]/bv[width<=1080]+ba[ext=m4a]/bv[width<=1080]+ba",
|
|
|
|
"1440p": "bv[width<=1440]+ba[ext=webm]/bv[width<=1440]+ba[ext=m4a]/bv[width<=1440]+ba",
|
|
|
|
"2160p": "bv[width<=2160]+ba[ext=webm]/bv[width<=2160]+ba[ext=m4a]/bv[width<=2160]+ba",
|
|
|
|
"mp3": "ba[filesize<500M]",
|
|
|
|
"m4a": "ba[ext=m4a][filesize<500M]",
|
|
|
|
"opus": "ba[ext=webm][filesize<500M]",
|
|
|
|
"vorbis": "ba[ext=webm][filesize<500M]",
|
|
|
|
"ogg": "ba[ext=webm][filesize<500M]",
|
2024-01-02 22:29:25 +00:00
|
|
|
}
|
|
|
|
self.default_options = {
|
|
|
|
"noplaylist": True,
|
|
|
|
"nocheckcertificate": True,
|
|
|
|
"no_color": True,
|
|
|
|
"noprogress": True,
|
|
|
|
"logger": self.log,
|
2024-05-31 02:28:56 +01:00
|
|
|
"format": "((bv+ba/b)[vcodec!=h265][filesize<500M]/b[filesize<=500M]/b)",
|
2024-04-02 02:55:54 +01:00
|
|
|
"outtmpl": "%(title).50s.%(ext)s",
|
2024-01-02 22:29:25 +00:00
|
|
|
"format_sort": [
|
|
|
|
"vcodec:h264",
|
|
|
|
"acodec:aac",
|
|
|
|
"vcodec:vp9",
|
|
|
|
"acodec:opus",
|
|
|
|
"acodec:vorbis",
|
|
|
|
"vcodec:vp8",
|
|
|
|
"ext",
|
|
|
|
],
|
|
|
|
"merge_output_format": "webm/mp4/mov/m4a/oga/ogg/mp3/mka/mkv",
|
|
|
|
"source_address": "0.0.0.0",
|
|
|
|
"concurrent_fragment_downloads": 4,
|
2024-01-26 19:05:23 +00:00
|
|
|
# "max_filesize": (25 * 1024 * 1024) - 256
|
2024-01-02 22:29:25 +00:00
|
|
|
}
|
|
|
|
self.colours = {
|
2024-04-16 00:46:26 +01:00
|
|
|
"youtube.com": 0xFF0000,
|
|
|
|
"youtu.be": 0xFF0000,
|
2024-01-02 22:29:25 +00:00
|
|
|
"tiktok.com": 0x25F5EF,
|
2024-04-16 00:46:26 +01:00
|
|
|
"instagram.com": 0xE1306C,
|
|
|
|
"shronk.net": 0xFFF952,
|
2024-01-02 22:29:25 +00:00
|
|
|
}
|
|
|
|
|
2024-01-09 09:34:55 +00:00
|
|
|
async def _init_db(self):
|
|
|
|
async with aiosqlite.connect("./data/ytdl.db") as db:
|
|
|
|
await db.execute(
|
|
|
|
"""
|
|
|
|
CREATE TABLE IF NOT EXISTS downloads (
|
|
|
|
key TEXT PRIMARY KEY,
|
|
|
|
message_id INTEGER NOT NULL UNIQUE,
|
|
|
|
channel_id INTEGER NOT NULL,
|
|
|
|
webpage_url TEXT NOT NULL,
|
|
|
|
format_id TEXT NOT NULL,
|
2024-01-09 09:38:25 +00:00
|
|
|
attachment_index INTEGER NOT NULL DEFAULT 0
|
2024-01-09 09:34:55 +00:00
|
|
|
)
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
await db.commit()
|
2024-01-09 09:37:19 +00:00
|
|
|
return
|
2024-01-09 09:34:55 +00:00
|
|
|
|
2024-03-06 20:47:06 +00:00
|
|
|
async def save_link(
|
2024-04-16 00:46:26 +01:00
|
|
|
self,
|
|
|
|
message: discord.Message,
|
|
|
|
webpage_url: str,
|
|
|
|
format_id: str,
|
|
|
|
attachment_index: int = 0,
|
|
|
|
*,
|
|
|
|
snip: typing.Optional[str] = None,
|
2024-03-06 20:47:06 +00:00
|
|
|
):
|
2024-01-09 09:34:55 +00:00
|
|
|
"""
|
|
|
|
Saves a link to discord to prevent having to re-download it.
|
|
|
|
:param message: The download message with the attachment.
|
|
|
|
:param webpage_url: The "webpage_url" key of the metadata
|
|
|
|
:param format_id: The "format_Id" key of the metadata
|
|
|
|
:param attachment_index: The index of the attachment. Defaults to 0
|
2024-03-06 20:47:06 +00:00
|
|
|
:param snip: The start and end time to snip the video. e.g. 00:00:00-00:10:00
|
2024-01-09 09:34:55 +00:00
|
|
|
:return: The created hash key
|
|
|
|
"""
|
2024-04-16 00:46:26 +01:00
|
|
|
snip = snip or "*"
|
2024-04-29 01:29:48 +01:00
|
|
|
_hash = hashlib.md5(f"{webpage_url}:{format_id}:{snip}".encode()).hexdigest()
|
|
|
|
try:
|
|
|
|
await self._init_db()
|
|
|
|
except Exception as e:
|
|
|
|
logging.error("Failed to initialise ytdl database: %s", e, exc_info=True)
|
|
|
|
return
|
2024-01-09 09:37:19 +00:00
|
|
|
async with aiosqlite.connect("./data/ytdl.db") as db:
|
2024-01-09 09:47:01 +00:00
|
|
|
self.log.debug(
|
2024-03-06 20:47:06 +00:00
|
|
|
"Saving %r (%r:%r:%r) with message %d>%d, index %d",
|
2024-01-09 09:47:01 +00:00
|
|
|
_hash,
|
2024-01-09 09:53:31 +00:00
|
|
|
webpage_url,
|
|
|
|
format_id,
|
2024-03-06 20:47:06 +00:00
|
|
|
snip,
|
2024-01-09 09:47:01 +00:00
|
|
|
message.channel.id,
|
|
|
|
message.id,
|
2024-04-16 00:46:26 +01:00
|
|
|
attachment_index,
|
2024-01-09 09:47:01 +00:00
|
|
|
)
|
2024-01-09 09:34:55 +00:00
|
|
|
await db.execute(
|
|
|
|
"""
|
|
|
|
INSERT INTO downloads (key, message_id, channel_id, webpage_url, format_id, attachment_index)
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
2024-01-24 21:23:10 +00:00
|
|
|
ON CONFLICT (key) DO UPDATE SET
|
|
|
|
message_id=excluded.message_id,
|
|
|
|
channel_id=excluded.channel_id,
|
|
|
|
attachment_index=excluded.attachment_index
|
2024-01-09 09:34:55 +00:00
|
|
|
""",
|
2024-04-16 00:46:26 +01:00
|
|
|
(_hash, message.id, message.channel.id, webpage_url, format_id, attachment_index),
|
2024-01-09 09:34:55 +00:00
|
|
|
)
|
|
|
|
await db.commit()
|
|
|
|
return _hash
|
|
|
|
|
2024-04-16 00:46:26 +01:00
|
|
|
async def get_saved(self, webpage_url: str, format_id: str, snip: str) -> typing.Optional[str]:
|
2024-01-09 09:34:55 +00:00
|
|
|
"""
|
|
|
|
Attempts to retrieve the attachment URL of a previously saved download.
|
|
|
|
:param webpage_url: The webpage url
|
|
|
|
:param format_id: The format ID
|
2024-03-06 20:47:06 +00:00
|
|
|
:param snip: The start and end time to snip the video. e.g. 00:00:00-00:10:00
|
2024-01-09 09:34:55 +00:00
|
|
|
:return: the URL, if found and valid.
|
|
|
|
"""
|
2024-04-29 01:29:48 +01:00
|
|
|
try:
|
|
|
|
await self._init_db()
|
|
|
|
except Exception as e:
|
|
|
|
logging.error("Failed to initialise ytdl database: %s", e, exc_info=True)
|
|
|
|
return
|
2024-01-09 09:37:19 +00:00
|
|
|
async with aiosqlite.connect("./data/ytdl.db") as db:
|
2024-03-06 20:47:06 +00:00
|
|
|
_hash = hashlib.md5(f"{webpage_url}:{format_id}:{snip}".encode()).hexdigest()
|
2024-01-09 09:47:01 +00:00
|
|
|
self.log.debug(
|
2024-04-16 00:46:26 +01:00
|
|
|
"Attempting to find a saved download for '%s:%s:%s' (%r).", webpage_url, format_id, snip, _hash
|
2024-01-09 09:47:01 +00:00
|
|
|
)
|
2024-01-09 09:34:55 +00:00
|
|
|
cursor = await db.execute(
|
2024-04-16 00:46:26 +01:00
|
|
|
"SELECT message_id, channel_id, attachment_index FROM downloads WHERE key=?", (_hash,)
|
2024-01-09 09:34:55 +00:00
|
|
|
)
|
|
|
|
entry = await cursor.fetchone()
|
|
|
|
if not entry:
|
2024-01-09 09:47:01 +00:00
|
|
|
self.log.debug("There was no saved download.")
|
2024-01-09 09:34:55 +00:00
|
|
|
return
|
|
|
|
message_id, channel_id, attachment_index = entry
|
|
|
|
channel = self.bot.get_channel(channel_id)
|
|
|
|
if not channel:
|
2024-01-09 09:47:01 +00:00
|
|
|
self.log.debug("Channel %r was not found.", channel_id)
|
2024-01-09 09:34:55 +00:00
|
|
|
return
|
|
|
|
try:
|
|
|
|
message = await channel.fetch_message(message_id)
|
|
|
|
except discord.HTTPException:
|
2024-01-09 09:47:01 +00:00
|
|
|
self.log.debug("%r did not contain a message with ID %r", channel, message_id)
|
2024-01-09 09:34:55 +00:00
|
|
|
await db.execute("DELETE FROM downloads WHERE key=?", (_hash,))
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
2024-01-09 09:47:01 +00:00
|
|
|
url = message.attachments[attachment_index].url
|
|
|
|
self.log.debug("Found URL %r, returning.", url)
|
2024-01-09 09:57:41 +00:00
|
|
|
return url
|
2024-01-09 09:34:55 +00:00
|
|
|
except IndexError:
|
2024-01-09 09:47:01 +00:00
|
|
|
self.log.debug("Attachment index %d is out of range (%r)", attachment_index, message.attachments)
|
2024-01-09 09:34:55 +00:00
|
|
|
return
|
2024-03-06 20:47:06 +00:00
|
|
|
|
2024-01-24 21:21:17 +00:00
|
|
|
def convert_to_m4a(self, file: Path) -> Path:
|
|
|
|
"""
|
|
|
|
Converts a file to m4a format.
|
|
|
|
:param file: The file to convert
|
|
|
|
:return: The converted file
|
|
|
|
"""
|
|
|
|
new_file = file.with_suffix(".m4a")
|
|
|
|
args = [
|
|
|
|
"-vn",
|
|
|
|
"-sn",
|
|
|
|
"-i",
|
|
|
|
str(file),
|
|
|
|
"-c:a",
|
|
|
|
"aac",
|
|
|
|
"-b:a",
|
2024-01-24 21:25:03 +00:00
|
|
|
"96k",
|
2024-01-24 21:21:17 +00:00
|
|
|
"-movflags",
|
|
|
|
"faststart",
|
|
|
|
"-y",
|
2024-04-16 00:46:26 +01:00
|
|
|
str(new_file),
|
2024-01-24 21:21:17 +00:00
|
|
|
]
|
2024-01-26 19:13:31 +00:00
|
|
|
self.log.debug("Running command: ffmpeg %s", " ".join(args))
|
2024-04-16 00:46:26 +01:00
|
|
|
process = subprocess.run(["ffmpeg", *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
2024-01-24 21:21:17 +00:00
|
|
|
if process.returncode != 0:
|
|
|
|
raise RuntimeError(process.stderr.decode())
|
|
|
|
return new_file
|
2024-01-09 09:34:55 +00:00
|
|
|
|
2024-05-31 02:23:42 +01:00
|
|
|
@staticmethod
|
2024-05-31 02:31:31 +01:00
|
|
|
async def upload_to_0x0(name: str, data: typing.IO[bytes], mime_type: str | None = None) -> str:
|
2024-05-31 02:23:42 +01:00
|
|
|
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()
|
|
|
|
|
2024-01-02 22:29:25 +00:00
|
|
|
@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)
|
|
|
|
async def yt_dl_command(
|
2024-04-16 00:46:26 +01:00
|
|
|
self,
|
|
|
|
ctx: discord.ApplicationContext,
|
|
|
|
url: typing.Annotated[str, discord.Option(str, description="The URL to download from.", required=True)],
|
|
|
|
user_format: typing.Annotated[
|
|
|
|
typing.Optional[str],
|
|
|
|
discord.Option(
|
2024-01-02 22:29:25 +00:00
|
|
|
str,
|
2024-04-16 00:46:26 +01:00
|
|
|
name="format",
|
|
|
|
description="The name of the format to download. Can also specify resolutions for youtube.",
|
|
|
|
required=False,
|
|
|
|
default=None,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
audio_only: typing.Annotated[
|
|
|
|
bool,
|
|
|
|
discord.Option(
|
2024-01-02 22:29:25 +00:00
|
|
|
bool,
|
2024-04-16 00:46:26 +01:00
|
|
|
name="audio-only",
|
|
|
|
description="Whether to convert result into an m4a file. Overwrites `format` if True.",
|
|
|
|
required=False,
|
|
|
|
default=False,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
snip: typing.Annotated[
|
|
|
|
typing.Optional[str],
|
|
|
|
discord.Option(description="A start and end position to trim. e.g. 00:00:00-00:10:00.", required=False),
|
|
|
|
],
|
2024-01-02 22:29:25 +00:00
|
|
|
):
|
|
|
|
"""Runs yt-dlp and outputs into discord."""
|
|
|
|
await ctx.defer()
|
2024-05-31 03:03:22 +01:00
|
|
|
last_edit = time.time()
|
2024-01-02 22:29:25 +00:00
|
|
|
options = self.default_options.copy()
|
2024-05-31 03:03:22 +01:00
|
|
|
|
|
|
|
async def _download_hook(_data: dict[str, typing.Any]):
|
|
|
|
n = time.time()
|
|
|
|
_total = _data.get("total_bytes", _data.get("total_bytes_estimate"))
|
|
|
|
if _total:
|
|
|
|
_percent = round(_data.get("downloaded_bytes", 0) / _total * 100, 2)
|
|
|
|
else:
|
|
|
|
_total = max(1, _data.get("fragment_count", 4096))
|
|
|
|
_percent = round(max(_data.get("fragment_index", 1), 1) / _total * 100, 2)
|
|
|
|
_speed_bytes_per_second = _data.get("speed", 1)
|
|
|
|
_speed_megabits_per_second = round((_speed_bytes_per_second * 8) / 1024 / 1024)
|
|
|
|
_eta = discord.utils.utcnow() + _data.get("eta") or discord.utils.utcnow()
|
|
|
|
blocks = "#" * math.floor(_percent / 10)
|
|
|
|
bar = f"{blocks}{'.' * (10 - len(blocks))}"
|
|
|
|
line = (f"{_percent}% [{bar}] | {_speed_megabits_per_second}Mbps | "
|
|
|
|
f"ETA {discord.utils.format_dt(_eta, 'R')}")
|
|
|
|
nonlocal last_edit
|
|
|
|
if (n - last_edit) >= 5:
|
|
|
|
embed.clear_fields()
|
|
|
|
embed.add_field(name="Progress", value=line)
|
|
|
|
await ctx.edit(embed=embed)
|
|
|
|
last_edit = time.time()
|
|
|
|
options["progress_hooks"] = [_download_hook]
|
|
|
|
|
2024-01-02 22:29:25 +00:00
|
|
|
description = ""
|
|
|
|
|
|
|
|
with tempfile.TemporaryDirectory(prefix="jimmy-ytdl-") as temp_dir:
|
|
|
|
temp_dir = Path(temp_dir)
|
|
|
|
paths = {
|
|
|
|
target: str(temp_dir)
|
|
|
|
for target in (
|
|
|
|
"home",
|
|
|
|
"temp",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
chosen_format = self.default_options["format"]
|
|
|
|
if user_format:
|
|
|
|
if user_format in self.common_formats:
|
|
|
|
chosen_format = self.common_formats[user_format]
|
|
|
|
else:
|
|
|
|
chosen_format = user_format
|
|
|
|
|
|
|
|
if audio_only:
|
|
|
|
# Overwrite format here to be best audio under 25 megabytes.
|
|
|
|
chosen_format = "ba[filesize<20M]"
|
|
|
|
# Also force sorting by the best audio bitrate first.
|
2024-04-16 00:46:26 +01:00
|
|
|
options["format_sort"] = ["abr", "br"]
|
2024-01-02 22:29:25 +00:00
|
|
|
options["postprocessors"] = [
|
|
|
|
{"key": "FFmpegExtractAudio", "preferredquality": "96", "preferredcodec": "best"}
|
|
|
|
]
|
|
|
|
options["format"] = chosen_format
|
|
|
|
options["paths"] = paths
|
|
|
|
|
|
|
|
with yt_dlp.YoutubeDL(options) as downloader:
|
2024-04-16 00:46:26 +01:00
|
|
|
await ctx.respond(embed=discord.Embed().set_footer(text="Downloading (step 1/10)"))
|
2024-01-02 22:29:25 +00:00
|
|
|
try:
|
|
|
|
# noinspection PyTypeChecker
|
|
|
|
extracted_info = await asyncio.to_thread(downloader.extract_info, url, download=False)
|
|
|
|
except yt_dlp.utils.DownloadError as e:
|
2024-01-10 11:00:54 +00:00
|
|
|
extracted_info = {
|
|
|
|
"title": "error",
|
|
|
|
"thumbnail_url": None,
|
|
|
|
"webpage_url": url,
|
|
|
|
"format": "error",
|
|
|
|
"format_id": "-1",
|
|
|
|
"ext": "wav",
|
|
|
|
"format_note": str(e),
|
|
|
|
"resolution": "1x1",
|
|
|
|
"fps": "1",
|
|
|
|
"vcodec": "error",
|
|
|
|
"acodec": "error",
|
2024-04-16 00:46:26 +01:00
|
|
|
"filesize": 0,
|
2024-01-10 11:00:54 +00:00
|
|
|
}
|
2024-01-02 22:29:25 +00:00
|
|
|
title = "error"
|
|
|
|
description = str(e)
|
|
|
|
thumbnail_url = webpage_url = None
|
2024-01-20 23:08:42 +00:00
|
|
|
likes = views = 0
|
2024-05-31 02:23:42 +01:00
|
|
|
chosen_format_id = str(uuid.uuid4())
|
2024-01-02 22:29:25 +00:00
|
|
|
else:
|
|
|
|
title = extracted_info.get("title", url)
|
|
|
|
title = textwrap.shorten(title, 100)
|
|
|
|
thumbnail_url = extracted_info.get("thumbnail") or None
|
2024-04-02 02:55:54 +01:00
|
|
|
webpage_url = extracted_info.get("webpage_url", url)
|
2024-01-02 22:29:25 +00:00
|
|
|
|
|
|
|
chosen_format = extracted_info.get("format")
|
|
|
|
chosen_format_id = extracted_info.get("format_id")
|
|
|
|
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")
|
2024-05-31 02:23:42 +01:00
|
|
|
fps = extracted_info.get("fps", 0.0)
|
2024-01-02 22:29:25 +00:00
|
|
|
vcodec = extracted_info.get("vcodec")
|
|
|
|
acodec = extracted_info.get("acodec")
|
|
|
|
filesize = extracted_info.get("filesize", extracted_info.get("filesize_approx", 1))
|
2024-01-20 23:08:42 +00:00
|
|
|
likes = extracted_info.get("like_count", extracted_info.get("average_rating", 0))
|
|
|
|
views = extracted_info.get("view_count", 0)
|
2024-01-02 22:29:25 +00:00
|
|
|
|
|
|
|
lines = []
|
|
|
|
if chosen_format and chosen_format_id:
|
|
|
|
lines.append(
|
|
|
|
"* Chosen format: `%s` (`%s`)" % (chosen_format, chosen_format_id),
|
|
|
|
)
|
|
|
|
if format_note:
|
|
|
|
lines.append("* Format note: %r" % format_note)
|
|
|
|
if final_extension:
|
|
|
|
lines.append("* File extension: " + final_extension)
|
|
|
|
if resolution:
|
|
|
|
_s = resolution
|
|
|
|
if fps:
|
|
|
|
_s += " @ %s FPS" % fps
|
|
|
|
lines.append("* Resolution: " + _s)
|
|
|
|
if vcodec or acodec:
|
|
|
|
lines.append("%s+%s" % (vcodec or "N/A", acodec or "N/A"))
|
|
|
|
if filesize:
|
|
|
|
lines.append("* Filesize: %s" % yt_dlp.utils.format_bytes(filesize))
|
|
|
|
|
|
|
|
if lines:
|
|
|
|
description += "\n"
|
|
|
|
description += "\n".join(lines)
|
|
|
|
|
|
|
|
domain = urlparse(webpage_url).netloc
|
2024-05-31 03:03:22 +01:00
|
|
|
embed = discord.Embed(
|
|
|
|
title=title,
|
|
|
|
description=description,
|
|
|
|
url=webpage_url,
|
|
|
|
colour=self.colours.get(domain, discord.Colour.og_blurple()),
|
|
|
|
)
|
|
|
|
embed.add_field(
|
|
|
|
name="Progress",
|
|
|
|
value="0% [..........] <t:253402300800:R>"
|
|
|
|
)
|
|
|
|
embed.set_footer(text="Downloading (step 2/10)")
|
|
|
|
embed.set_thumbnail(url=thumbnail_url)
|
2024-01-02 22:29:25 +00:00
|
|
|
await ctx.edit(
|
2024-05-31 03:03:22 +01:00
|
|
|
embed=embed
|
2024-01-02 22:29:25 +00:00
|
|
|
)
|
2024-05-31 02:23:42 +01:00
|
|
|
previous = await self.get_saved(webpage_url, chosen_format_id, snip or "*")
|
2024-01-09 09:34:55 +00:00
|
|
|
if previous:
|
|
|
|
await ctx.edit(
|
2024-01-09 09:59:07 +00:00
|
|
|
content=previous,
|
2024-01-09 09:34:55 +00:00
|
|
|
embed=discord.Embed(
|
|
|
|
title=f"Downloaded {title}!",
|
|
|
|
description="Used previously downloaded attachment.",
|
|
|
|
colour=discord.Colour.green(),
|
|
|
|
timestamp=discord.utils.utcnow(),
|
|
|
|
url=previous,
|
2024-04-16 00:46:26 +01:00
|
|
|
fields=[discord.EmbedField(name="URL", value=previous, inline=False)],
|
|
|
|
).set_image(url=previous),
|
2024-01-09 09:34:55 +00:00
|
|
|
)
|
|
|
|
return
|
2024-05-31 03:03:22 +01:00
|
|
|
|
|
|
|
last_edit = time.time()
|
|
|
|
|
2024-01-02 22:29:25 +00:00
|
|
|
try:
|
|
|
|
await asyncio.to_thread(functools.partial(downloader.download, [url]))
|
|
|
|
except yt_dlp.DownloadError as e:
|
|
|
|
logging.error(e, exc_info=True)
|
|
|
|
return await ctx.edit(
|
|
|
|
embed=discord.Embed(
|
|
|
|
title="Error",
|
|
|
|
description=f"Download failed:\n```\n{e}\n```",
|
|
|
|
colour=discord.Colour.red(),
|
|
|
|
url=webpage_url,
|
|
|
|
),
|
|
|
|
delete_after=120,
|
|
|
|
)
|
|
|
|
try:
|
2024-01-24 21:17:37 +00:00
|
|
|
if audio_only is False:
|
2024-05-31 02:23:42 +01:00
|
|
|
file: Path = next(temp_dir.glob("*." + extracted_info["ext"]))
|
2024-01-24 21:17:37 +00:00
|
|
|
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"):
|
2024-05-31 02:23:42 +01:00
|
|
|
file: Path = _file
|
2024-01-24 21:17:37 +00:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise StopIteration
|
2024-01-02 22:29:25 +00:00
|
|
|
except StopIteration:
|
2024-05-31 02:23:42 +01:00
|
|
|
ext = extracted_info["ext"]
|
2024-01-24 21:17:37 +00:00
|
|
|
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.",
|
2024-05-31 02:23:42 +01:00
|
|
|
ext,
|
2024-04-16 00:46:26 +01:00
|
|
|
list(map(str, temp_dir.iterdir())),
|
2024-01-24 21:17:37 +00:00
|
|
|
)
|
2024-01-02 22:29:25 +00:00
|
|
|
return await ctx.edit(
|
|
|
|
embed=discord.Embed(
|
|
|
|
title="Error",
|
2024-05-31 02:23:42 +01:00
|
|
|
description="Failed to locate downloaded video file."
|
|
|
|
f" Was expecting a file with the extension {ext}.\n"
|
2024-04-16 00:46:26 +01:00
|
|
|
f"Files: {', '.join(list(map(str, temp_dir.iterdir())))}",
|
2024-01-02 22:29:25 +00:00
|
|
|
colour=discord.Colour.red(),
|
2024-04-16 00:46:26 +01:00
|
|
|
url=webpage_url,
|
2024-01-02 22:29:25 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
if snip:
|
|
|
|
try:
|
|
|
|
trim_start, trim_end = snip.split("-")
|
|
|
|
except ValueError:
|
|
|
|
trim_start, trim_end = snip, None
|
|
|
|
trim_start = trim_start or "00:00:00"
|
|
|
|
trim_end = trim_end or extracted_info.get("duration_string", "00:30:00")
|
2024-02-28 18:17:55 +00:00
|
|
|
new_file = temp_dir / ("output" + file.suffix)
|
2024-01-02 22:29:25 +00:00
|
|
|
args = [
|
|
|
|
"-hwaccel",
|
|
|
|
"auto",
|
|
|
|
"-i",
|
|
|
|
str(file),
|
2024-02-28 18:17:55 +00:00
|
|
|
"-ss",
|
|
|
|
trim_start,
|
2024-01-02 22:29:25 +00:00
|
|
|
"-to",
|
|
|
|
trim_end,
|
|
|
|
"-preset",
|
2024-02-28 18:17:55 +00:00
|
|
|
"fast",
|
2024-01-02 22:29:25 +00:00
|
|
|
"-crf",
|
2024-05-31 02:23:42 +01:00
|
|
|
"24",
|
2024-01-02 22:29:25 +00:00
|
|
|
"-deadline",
|
|
|
|
"realtime",
|
|
|
|
"-cpu-used",
|
|
|
|
"5",
|
|
|
|
"-movflags",
|
|
|
|
"faststart",
|
|
|
|
"-b:a",
|
2024-01-24 21:25:03 +00:00
|
|
|
"96k",
|
2024-01-02 22:29:25 +00:00
|
|
|
"-y",
|
|
|
|
"-strict",
|
|
|
|
"2",
|
2024-04-16 00:46:26 +01:00
|
|
|
str(new_file),
|
2024-01-02 22:29:25 +00:00
|
|
|
]
|
|
|
|
async with ctx.channel.typing():
|
|
|
|
await ctx.edit(
|
|
|
|
embed=discord.Embed(
|
|
|
|
title=f"Trimming from {trim_start} to {trim_end}.",
|
|
|
|
description="Please wait, this may take a couple of minutes.",
|
|
|
|
colour=discord.Colour.og_blurple(),
|
2024-04-16 00:46:26 +01:00
|
|
|
timestamp=discord.utils.utcnow(),
|
2024-01-02 22:29:25 +00:00
|
|
|
)
|
|
|
|
)
|
2024-02-28 18:17:55 +00:00
|
|
|
self.log.debug("Running command: 'ffmpeg %s'", " ".join(args))
|
2024-01-02 22:29:25 +00:00
|
|
|
process = await asyncio.create_subprocess_exec(
|
2024-04-16 00:46:26 +01:00
|
|
|
"ffmpeg", *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
2024-01-02 22:29:25 +00:00
|
|
|
)
|
|
|
|
stdout, stderr = await process.communicate()
|
2024-02-28 18:17:55 +00:00
|
|
|
self.log.debug("STDOUT:\n%r", stdout.decode())
|
|
|
|
self.log.debug("STDERR:\n%r", stderr.decode())
|
2024-01-02 22:29:25 +00:00
|
|
|
if process.returncode != 0:
|
|
|
|
return await ctx.edit(
|
|
|
|
embed=discord.Embed(
|
|
|
|
title="Error",
|
|
|
|
description=f"Trimming failed:\n```\n{stderr.decode()}\n```",
|
|
|
|
colour=discord.Colour.red(),
|
2024-04-16 00:46:26 +01:00
|
|
|
url=webpage_url,
|
2024-01-02 22:29:25 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
file = new_file
|
2024-03-06 20:47:06 +00:00
|
|
|
|
2024-02-28 18:17:55 +00:00
|
|
|
if audio_only and file.suffix != ".m4a":
|
2024-01-24 21:21:17 +00:00
|
|
|
self.log.info("Converting %r to m4a.", file)
|
2024-05-31 02:23:42 +01:00
|
|
|
file: Path = await asyncio.to_thread(self.convert_to_m4a, file)
|
2024-01-02 22:29:25 +00:00
|
|
|
|
|
|
|
stat = file.stat()
|
|
|
|
size_bytes = stat.st_size
|
2024-05-31 02:23:42 +01:00
|
|
|
if size_bytes >= ((500 * 1024 * 1024) - 256):
|
2024-01-02 22:29:25 +00:00
|
|
|
return await ctx.edit(
|
|
|
|
embed=discord.Embed(
|
|
|
|
title="Error",
|
|
|
|
description=f"File is too large to upload ({round(size_bytes / 1024 / 1024)}MB).",
|
|
|
|
colour=discord.Colour.red(),
|
2024-04-16 00:46:26 +01:00
|
|
|
url=webpage_url,
|
2024-01-02 22:29:25 +00:00
|
|
|
)
|
|
|
|
)
|
2024-05-31 02:23:42 +01:00
|
|
|
|
2024-01-02 22:29:25 +00:00
|
|
|
size_megabits = (size_bytes * 8) / 1024 / 1024
|
|
|
|
eta_seconds = size_megabits / 20
|
|
|
|
await ctx.edit(
|
|
|
|
embed=discord.Embed(
|
|
|
|
title="Uploading...",
|
|
|
|
description=f"ETA <t:{int(eta_seconds + discord.utils.utcnow().timestamp()) + 2}:R>",
|
|
|
|
colour=discord.Colour.og_blurple(),
|
2024-04-16 00:46:26 +01:00
|
|
|
timestamp=discord.utils.utcnow(),
|
2024-01-02 22:29:25 +00:00
|
|
|
)
|
|
|
|
)
|
2024-05-31 02:23:42 +01:00
|
|
|
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,
|
|
|
|
)
|
2024-01-02 22:29:25 +00:00
|
|
|
try:
|
2024-05-31 02:28:56 +01:00
|
|
|
if size_bytes >= (20 * 1024 * 1024) or vcodec.lower() in ["hevc", "h265", "av1", "av01"]:
|
2024-05-31 02:23:42 +01:00
|
|
|
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:
|
2024-01-02 22:29:25 +00:00
|
|
|
self.log.error(e, exc_info=True)
|
|
|
|
return await ctx.edit(
|
|
|
|
embed=discord.Embed(
|
|
|
|
title="Error",
|
|
|
|
description=f"Upload failed:\n```\n{e}\n```",
|
|
|
|
colour=discord.Colour.red(),
|
2024-04-16 00:46:26 +01:00
|
|
|
url=webpage_url,
|
2024-01-02 22:29:25 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2024-02-06 23:03:49 +00:00
|
|
|
|
2024-01-02 22:29:25 +00:00
|
|
|
def setup(bot):
|
|
|
|
bot.add_cog(YTDLCog(bot))
|