ytdlbot/downloader.py

192 lines
6.3 KiB
Python
Raw Normal View History

2021-08-14 17:57:42 +08:00
#!/usr/local/bin/python3
# coding: utf-8
# ytdlbot - downloader.py
# 8/14/21 16:53
#
__author__ = "Benny <benny.think@gmail.com>"
import logging
import os
import pathlib
import re
2021-08-14 17:57:42 +08:00
import subprocess
2021-08-25 22:13:25 +08:00
import time
2021-08-14 17:57:42 +08:00
import fakeredis
import filetype
2021-10-19 18:58:48 +08:00
if os.getenv("downloader") == "youtube-dl":
import youtube_dl as ytdl
from youtube_dl import DownloadError
else:
import yt_dlp as ytdl
from yt_dlp import DownloadError
2021-08-14 17:57:42 +08:00
2021-08-29 10:02:11 +08:00
from config import ENABLE_VIP
from db import Redis
from limit import VIP
from utils import adjust_formats, apply_log_formatter, get_user_settings
2021-08-16 09:00:27 +08:00
2021-08-14 17:57:42 +08:00
r = fakeredis.FakeStrictRedis()
EXPIRE = 5
apply_log_formatter()
2021-08-14 17:57:42 +08:00
def sizeof_fmt(num: int, suffix='B'):
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
def edit_text(bot_msg, text):
2021-08-25 22:13:25 +08:00
key = f"{bot_msg.chat.id}-{bot_msg.message_id}"
2021-08-14 17:57:42 +08:00
# if the key exists, we shouldn't send edit message
if not r.exists(key):
r.set(key, "ok", ex=EXPIRE)
bot_msg.edit_text(text)
def remove_bash_color(text):
return re.sub(r'\u001b|\[0;94m|\u001b\[0m|\[0;32m|\[0m', "", text)
2021-08-14 17:57:42 +08:00
def download_hook(d: dict, bot_msg):
if d['status'] == 'downloading':
downloaded = d.get("downloaded_bytes", 0)
total = d.get("total_bytes") or d.get("total_bytes_estimate", 0)
2021-08-25 22:13:25 +08:00
# total = 0
2021-08-14 17:57:42 +08:00
filesize = sizeof_fmt(total)
2021-08-26 20:12:26 +08:00
max_size = 2 * 1024 * 1024 * 1024
if total > max_size:
raise ValueError(f"\nYour video is too large. "
f"{filesize} will exceed Telegram's max limit {sizeof_fmt(max_size)}")
2021-08-14 17:57:42 +08:00
percent = remove_bash_color(d.get("_percent_str", "N/A"))
speed = remove_bash_color(d.get("_speed_str", "N/A"))
2021-08-29 10:02:11 +08:00
if ENABLE_VIP:
result, err_msg = check_quota(total, bot_msg.chat.id)
if result is False:
raise ValueError(err_msg)
2021-08-14 17:57:42 +08:00
text = f'[{filesize}]: Downloading {percent} - {downloaded}/{total} @ {speed}'
edit_text(bot_msg, text)
def upload_hook(current, total, bot_msg):
filesize = sizeof_fmt(total)
text = f'[{filesize}]: Uploading {round(current / total * 100, 2)}% - {current}/{total}'
edit_text(bot_msg, text)
2021-08-25 22:13:25 +08:00
def check_quota(file_size, chat_id) -> ("bool", "str"):
remain, _, ttl = VIP().check_remaining_quota(chat_id)
if file_size > remain:
refresh_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ttl + time.time()))
err = f"Quota exceed, you have {sizeof_fmt(remain)} remaining, " \
f"but you want to download a video with {sizeof_fmt(file_size)} in size. \n" \
f"Try again in {ttl} seconds({refresh_time})"
logging.warning(err)
Redis().update_metrics("quota_exceed")
return False, err
else:
return True, ""
def convert_to_mp4(resp: dict, bot_msg):
default_type = ["video/x-flv","video/webm"]
2021-08-14 17:57:42 +08:00
if resp["status"]:
# all_converted = []
for path in resp["filepath"]:
mime = filetype.guess(path).mime
if mime in default_type:
edit_text(bot_msg, f"Converting {os.path.basename(path)} to mp4. Please wait patiently.")
new_name = os.path.basename(path).split(".")[0] + ".mp4"
new_file_path = os.path.join(os.path.dirname(path), new_name)
2021-09-21 16:33:12 +08:00
cmd = ["ffmpeg", "-i", path, new_file_path]
logging.info("Detected %s, converting to mp4...", mime)
2021-09-21 16:33:12 +08:00
subprocess.check_output(cmd)
index = resp["filepath"].index(path)
resp["filepath"][index] = new_file_path
return resp
2021-08-14 17:57:42 +08:00
def ytdl_download(url, tempdir, bm) -> dict:
2021-08-25 22:13:25 +08:00
chat_id = bm.chat.id
response = {"status": True, "error": "", "filepath": []}
output = os.path.join(tempdir, '%(title).50s.%(ext)s')
2021-08-14 17:57:42 +08:00
ydl_opts = {
'progress_hooks': [lambda d: download_hook(d, bm)],
'outtmpl': output,
2021-08-25 22:13:25 +08:00
'restrictfilenames': False,
2021-08-14 17:57:42 +08:00
'quiet': True
}
formats = [
"bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio",
"bestvideo[vcodec^=avc]+bestaudio[acodec^=mp4a]/best[vcodec^=avc]/best",
""
]
adjust_formats(chat_id, url, formats)
2021-08-25 22:13:25 +08:00
# TODO it appears twitter download on macOS will fail. Don't know why...Linux's fine.
2021-08-14 17:57:42 +08:00
for f in formats:
if f:
ydl_opts["format"] = f
try:
logging.info("Downloading for %s with format %s", url, f)
2021-10-19 18:58:48 +08:00
with ytdl.YoutubeDL(ydl_opts) as ydl:
2021-08-14 17:57:42 +08:00
ydl.download([url])
2021-08-25 22:13:25 +08:00
response["status"] = True
response["error"] = ""
break
2021-08-14 17:57:42 +08:00
2021-08-25 22:13:25 +08:00
except DownloadError as e:
err = str(e)
logging.error("Download failed for %s ", url)
response["status"] = False
response["error"] = err
# can't return here
except ValueError as e:
response["status"] = False
response["error"] = str(e)
except Exception as e:
logging.error("UNKNOWN EXCEPTION: %s", e)
2021-08-25 22:13:25 +08:00
logging.info("%s - %s", url, response)
2021-08-25 22:13:25 +08:00
if response["status"] is False:
return response
for i in os.listdir(tempdir):
p: "str" = os.path.join(tempdir, i)
file_size = os.stat(p).st_size
2021-08-29 10:02:11 +08:00
if ENABLE_VIP:
remain, _, ttl = VIP().check_remaining_quota(chat_id)
result, err_msg = check_quota(file_size, chat_id)
else:
result, err_msg = True, ""
2021-08-25 22:13:25 +08:00
if result is False:
response["status"] = False
response["error"] = err_msg
else:
VIP().use_quota(bm.chat.id, file_size)
response["status"] = True
response["filepath"].append(p)
2021-08-25 22:13:25 +08:00
2021-08-14 17:57:42 +08:00
# convert format if necessary
settings = get_user_settings(str(chat_id))
if settings[2] == "video":
# only convert if send type is video
convert_to_mp4(response, bm)
2021-08-14 17:57:42 +08:00
return response
def convert_flac(flac_name, tmp):
logging.info("converting to flac")
flac_tmp = pathlib.Path(tmp.name).parent.joinpath(flac_name).as_posix()
2021-09-21 16:33:12 +08:00
cmd_list = ["ffmpeg", "-y", "-i", tmp.name, "-vn", "-acodec", "copy", flac_tmp]
logging.info("CMD: %s", cmd_list)
subprocess.check_output(cmd_list)
2021-08-14 17:57:42 +08:00
return flac_tmp