Path: blob/master/bot/modules/ytdlp.py
1630 views
from httpx import AsyncClient1from asyncio import wait_for, Event2from functools import partial3from pyrogram.filters import regex, user4from pyrogram.handlers import CallbackQueryHandler5from time import time6from yt_dlp import YoutubeDL78from .. import LOGGER, bot_loop, task_dict_lock, DOWNLOAD_DIR9from ..core.config_manager import Config10from ..helper.ext_utils.bot_utils import (11new_task,12sync_to_async,13arg_parser,14COMMAND_USAGE,15)16from ..helper.ext_utils.links_utils import is_url17from ..helper.ext_utils.status_utils import get_readable_file_size, get_readable_time18from ..helper.listeners.task_listener import TaskListener19from ..helper.mirror_leech_utils.download_utils.yt_dlp_download import YoutubeDLHelper20from ..helper.telegram_helper.button_build import ButtonMaker21from ..helper.telegram_helper.message_utils import (22send_message,23edit_message,24delete_message,25)262728@new_task29async def select_format(_, query, obj):30data = query.data.split()31message = query.message32await query.answer()3334if data[1] == "dict":35b_name = data[2]36await obj.qual_subbuttons(b_name)37elif data[1] == "mp3":38await obj.mp3_subbuttons()39elif data[1] == "audio":40await obj.audio_format()41elif data[1] == "aq":42if data[2] == "back":43await obj.audio_format()44else:45await obj.audio_quality(data[2])46elif data[1] == "back":47await obj.back_to_main()48elif data[1] == "cancel":49await edit_message(message, "Task has been cancelled.")50obj.qual = None51obj.listener.is_cancelled = True52obj.event.set()53else:54if data[1] == "sub":55obj.qual = obj.formats[data[2]][data[3]][1]56elif "|" in data[1]:57obj.qual = obj.formats[data[1]]58else:59obj.qual = data[1]60obj.event.set()616263class YtSelection:64def __init__(self, listener):65self.listener = listener66self._is_m4a = False67self._reply_to = None68self._time = time()69self._timeout = 12070self._is_playlist = False71self._main_buttons = None72self.event = Event()73self.formats = {}74self.qual = None7576async def _event_handler(self):77pfunc = partial(select_format, obj=self)78handler = self.listener.client.add_handler(79CallbackQueryHandler(80pfunc, filters=regex("^ytq") & user(self.listener.user_id)81),82group=-1,83)84try:85await wait_for(self.event.wait(), timeout=self._timeout)86except:87await edit_message(self._reply_to, "Timed Out. Task has been cancelled!")88self.qual = None89self.listener.is_cancelled = True90self.event.set()91finally:92self.listener.client.remove_handler(*handler)9394async def get_quality(self, result):95buttons = ButtonMaker()96if "entries" in result:97self._is_playlist = True98for i in ["144", "240", "360", "480", "720", "1080", "1440", "2160"]:99video_format = f"bv*[height<=?{i}][ext=mp4]+ba[ext=m4a]/b[height<=?{i}]"100b_data = f"{i}|mp4"101self.formats[b_data] = video_format102buttons.data_button(f"{i}-mp4", f"ytq {b_data}")103video_format = f"bv*[height<=?{i}][ext=webm]+ba/b[height<=?{i}]"104b_data = f"{i}|webm"105self.formats[b_data] = video_format106buttons.data_button(f"{i}-webm", f"ytq {b_data}")107buttons.data_button("MP3", "ytq mp3")108buttons.data_button("Audio Formats", "ytq audio")109buttons.data_button("Best Videos", "ytq bv*+ba/b")110buttons.data_button("Best Audios", "ytq ba/b")111buttons.data_button("Cancel", "ytq cancel", "footer")112self._main_buttons = buttons.build_menu(3)113msg = f"Choose Playlist Videos Quality:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"114else:115format_dict = result.get("formats")116if format_dict is not None:117for item in format_dict:118if item.get("tbr"):119format_id = item["format_id"]120121if item.get("filesize"):122size = item["filesize"]123elif item.get("filesize_approx"):124size = item["filesize_approx"]125else:126size = 0127128if item.get("video_ext") == "none" and (129item.get("resolution") == "audio only"130or item.get("acodec") != "none"131):132if item.get("audio_ext") == "m4a":133self._is_m4a = True134b_name = f"{item.get('acodec') or format_id}-{item['ext']}"135v_format = format_id136elif item.get("height"):137height = item["height"]138ext = item["ext"]139fps = item["fps"] if item.get("fps") else ""140b_name = f"{height}p{fps}-{ext}"141ba_ext = (142"[ext=m4a]" if self._is_m4a and ext == "mp4" else ""143)144v_format = f"{format_id}+ba{ba_ext}/b[height=?{height}]"145else:146continue147148self.formats.setdefault(b_name, {})[f"{item['tbr']}"] = [149size,150v_format,151]152153for b_name, tbr_dict in self.formats.items():154if len(tbr_dict) == 1:155tbr, v_list = next(iter(tbr_dict.items()))156buttonName = f"{b_name} ({get_readable_file_size(v_list[0])})"157buttons.data_button(buttonName, f"ytq sub {b_name} {tbr}")158else:159buttons.data_button(b_name, f"ytq dict {b_name}")160buttons.data_button("MP3", "ytq mp3")161buttons.data_button("Audio Formats", "ytq audio")162buttons.data_button("Best Video", "ytq bv*+ba/b")163buttons.data_button("Best Audio", "ytq ba/b")164buttons.data_button("Cancel", "ytq cancel", "footer")165self._main_buttons = buttons.build_menu(2)166msg = f"Choose Video Quality:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"167self._reply_to = await send_message(168self.listener.message, msg, self._main_buttons169)170await self._event_handler()171if not self.listener.is_cancelled:172await delete_message(self._reply_to)173return self.qual174175async def back_to_main(self):176if self._is_playlist:177msg = f"Choose Playlist Videos Quality:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"178else:179msg = f"Choose Video Quality:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"180await edit_message(self._reply_to, msg, self._main_buttons)181182async def qual_subbuttons(self, b_name):183buttons = ButtonMaker()184tbr_dict = self.formats[b_name]185for tbr, d_data in tbr_dict.items():186button_name = f"{tbr}K ({get_readable_file_size(d_data[0])})"187buttons.data_button(button_name, f"ytq sub {b_name} {tbr}")188buttons.data_button("Back", "ytq back", "footer")189buttons.data_button("Cancel", "ytq cancel", "footer")190subbuttons = buttons.build_menu(2)191msg = f"Choose Bit rate for <b>{b_name}</b>:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"192await edit_message(self._reply_to, msg, subbuttons)193194async def mp3_subbuttons(self):195i = "s" if self._is_playlist else ""196buttons = ButtonMaker()197audio_qualities = [64, 128, 320]198for q in audio_qualities:199audio_format = f"ba/b-mp3-{q}"200buttons.data_button(f"{q}K-mp3", f"ytq {audio_format}")201buttons.data_button("Back", "ytq back")202buttons.data_button("Cancel", "ytq cancel")203subbuttons = buttons.build_menu(3)204msg = f"Choose mp3 Audio{i} Bitrate:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"205await edit_message(self._reply_to, msg, subbuttons)206207async def audio_format(self):208i = "s" if self._is_playlist else ""209buttons = ButtonMaker()210for frmt in ["aac", "alac", "flac", "m4a", "opus", "vorbis", "wav"]:211audio_format = f"ba/b-{frmt}-"212buttons.data_button(frmt, f"ytq aq {audio_format}")213buttons.data_button("Back", "ytq back", "footer")214buttons.data_button("Cancel", "ytq cancel", "footer")215subbuttons = buttons.build_menu(3)216msg = f"Choose Audio{i} Format:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"217await edit_message(self._reply_to, msg, subbuttons)218219async def audio_quality(self, format):220i = "s" if self._is_playlist else ""221buttons = ButtonMaker()222for qual in range(11):223audio_format = f"{format}{qual}"224buttons.data_button(qual, f"ytq {audio_format}")225buttons.data_button("Back", "ytq aq back")226buttons.data_button("Cancel", "ytq aq cancel")227subbuttons = buttons.build_menu(5)228msg = f"Choose Audio{i} Quality:\n0 is best and 10 is worst\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"229await edit_message(self._reply_to, msg, subbuttons)230231232def extract_info(link, options):233with YoutubeDL(options) as ydl:234result = ydl.extract_info(link, download=False)235if result is None:236raise ValueError("Info result is None")237return result238239240async def _mdisk(link, name):241key = link.split("/")[-1]242async with AsyncClient(verify=False) as client:243resp = await client.get(244f"https://diskuploader.entertainvideo.com/v1/file/cdnurl?param={key}"245)246if resp.status_code == 200:247resp_json = resp.json()248link = resp_json["source"]249if not name:250name = resp_json["filename"]251return name, link252253254class YtDlp(TaskListener):255def __init__(256self,257client,258message,259_=None,260is_leech=False,261__=None,262___=None,263same_dir=None,264bulk=None,265multi_tag=None,266options="",267):268if same_dir is None:269same_dir = {}270if bulk is None:271bulk = []272self.message = message273self.client = client274self.multi_tag = multi_tag275self.options = options276self.same_dir = same_dir277self.bulk = bulk278super().__init__()279self.is_ytdlp = True280self.is_leech = is_leech281282async def new_event(self):283text = self.message.text.split("\n")284input_list = text[0].split(" ")285qual = ""286287args = {288"-doc": False,289"-med": False,290"-s": False,291"-b": False,292"-z": False,293"-sv": False,294"-ss": False,295"-f": False,296"-fd": False,297"-fu": False,298"-hl": False,299"-bt": False,300"-ut": False,301"-i": 0,302"-sp": 0,303"link": "",304"-m": "",305"-opt": {},306"-n": "",307"-up": "",308"-rcf": "",309"-t": "",310"-ca": "",311"-cv": "",312"-ns": "",313"-tl": "",314"-ff": set(),315}316317arg_parser(input_list[1:], args)318319try:320self.multi = int(args["-i"])321except:322self.multi = 0323324try:325opt = eval(args["-opt"]) if args["-opt"] else {}326except Exception as e:327LOGGER.error(e)328opt = {}329330self.ffmpeg_cmds = args["-ff"]331self.select = args["-s"]332self.name = args["-n"]333self.up_dest = args["-up"]334self.rc_flags = args["-rcf"]335self.link = args["link"]336self.compress = args["-z"]337self.thumb = args["-t"]338self.split_size = args["-sp"]339self.sample_video = args["-sv"]340self.screen_shots = args["-ss"]341self.force_run = args["-f"]342self.force_download = args["-fd"]343self.force_upload = args["-fu"]344self.convert_audio = args["-ca"]345self.convert_video = args["-cv"]346self.name_sub = args["-ns"]347self.hybrid_leech = args["-hl"]348self.thumbnail_layout = args["-tl"]349self.as_doc = args["-doc"]350self.as_med = args["-med"]351self.folder_name = f"/{args["-m"]}".rstrip("/") if len(args["-m"]) > 0 else ""352self.bot_trans = args["-bt"]353self.user_trans = args["-ut"]354355is_bulk = args["-b"]356357bulk_start = 0358bulk_end = 0359reply_to = None360361if not isinstance(is_bulk, bool):362dargs = is_bulk.split(":")363bulk_start = dargs[0] or None364if len(dargs) == 2:365bulk_end = dargs[1] or None366is_bulk = True367368if not is_bulk:369if self.multi > 0:370if self.folder_name:371async with task_dict_lock:372if self.folder_name in self.same_dir:373self.same_dir[self.folder_name]["tasks"].add(self.mid)374for fd_name in self.same_dir:375if fd_name != self.folder_name:376self.same_dir[fd_name]["total"] -= 1377elif self.same_dir:378self.same_dir[self.folder_name] = {379"total": self.multi,380"tasks": {self.mid},381}382for fd_name in self.same_dir:383if fd_name != self.folder_name:384self.same_dir[fd_name]["total"] -= 1385else:386self.same_dir = {387self.folder_name: {388"total": self.multi,389"tasks": {self.mid},390}391}392elif self.same_dir:393async with task_dict_lock:394for fd_name in self.same_dir:395self.same_dir[fd_name]["total"] -= 1396else:397await self.init_bulk(input_list, bulk_start, bulk_end, YtDlp)398return399400if len(self.bulk) != 0:401del self.bulk[0]402403path = f"{DOWNLOAD_DIR}{self.mid}{self.folder_name}"404405await self.get_tag(text)406407opt = opt or self.user_dict.get("YT_DLP_OPTIONS") or Config.YT_DLP_OPTIONS408409if not self.link and (reply_to := self.message.reply_to_message):410self.link = reply_to.text.split("\n", 1)[0].strip()411412if not is_url(self.link):413await send_message(414self.message, COMMAND_USAGE["yt"][0], COMMAND_USAGE["yt"][1]415)416await self.remove_from_same_dir()417return418419if "mdisk.me" in self.link:420self.name, self.link = await _mdisk(self.link, self.name)421422try:423await self.before_start()424except Exception as e:425await send_message(self.message, e)426await self.remove_from_same_dir()427return428options = {"usenetrc": True, "cookiefile": "cookies.txt"}429if opt:430for key, value in opt.items():431if key in ["postprocessors", "download_ranges"]:432continue433if key == "format" and not self.select:434if value.startswith("ba/b-"):435qual = value436continue437else:438qual = value439options[key] = value440options["playlist_items"] = "0"441try:442result = await sync_to_async(extract_info, self.link, options)443except Exception as e:444msg = str(e).replace("<", " ").replace(">", " ")445await send_message(self.message, f"{self.tag} {msg}")446await self.remove_from_same_dir()447return448finally:449await self.run_multi(input_list, YtDlp)450451if not qual:452qual = await YtSelection(self).get_quality(result)453if qual is None:454await self.remove_from_same_dir()455return456457LOGGER.info(f"Downloading with YT-DLP: {self.link}")458playlist = "entries" in result459ydl = YoutubeDLHelper(self)460await ydl.add_download(path, qual, playlist, opt)461462463async def ytdl(client, message):464bot_loop.create_task(YtDlp(client, message).new_event())465466467async def ytdl_leech(client, message):468bot_loop.create_task(YtDlp(client, message, is_leech=True).new_event())469470471