Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
anasty17
GitHub Repository: anasty17/mirror-leech-telegram-bot
Path: blob/master/bot/modules/ytdlp.py
1630 views
1
from httpx import AsyncClient
2
from asyncio import wait_for, Event
3
from functools import partial
4
from pyrogram.filters import regex, user
5
from pyrogram.handlers import CallbackQueryHandler
6
from time import time
7
from yt_dlp import YoutubeDL
8
9
from .. import LOGGER, bot_loop, task_dict_lock, DOWNLOAD_DIR
10
from ..core.config_manager import Config
11
from ..helper.ext_utils.bot_utils import (
12
new_task,
13
sync_to_async,
14
arg_parser,
15
COMMAND_USAGE,
16
)
17
from ..helper.ext_utils.links_utils import is_url
18
from ..helper.ext_utils.status_utils import get_readable_file_size, get_readable_time
19
from ..helper.listeners.task_listener import TaskListener
20
from ..helper.mirror_leech_utils.download_utils.yt_dlp_download import YoutubeDLHelper
21
from ..helper.telegram_helper.button_build import ButtonMaker
22
from ..helper.telegram_helper.message_utils import (
23
send_message,
24
edit_message,
25
delete_message,
26
)
27
28
29
@new_task
30
async def select_format(_, query, obj):
31
data = query.data.split()
32
message = query.message
33
await query.answer()
34
35
if data[1] == "dict":
36
b_name = data[2]
37
await obj.qual_subbuttons(b_name)
38
elif data[1] == "mp3":
39
await obj.mp3_subbuttons()
40
elif data[1] == "audio":
41
await obj.audio_format()
42
elif data[1] == "aq":
43
if data[2] == "back":
44
await obj.audio_format()
45
else:
46
await obj.audio_quality(data[2])
47
elif data[1] == "back":
48
await obj.back_to_main()
49
elif data[1] == "cancel":
50
await edit_message(message, "Task has been cancelled.")
51
obj.qual = None
52
obj.listener.is_cancelled = True
53
obj.event.set()
54
else:
55
if data[1] == "sub":
56
obj.qual = obj.formats[data[2]][data[3]][1]
57
elif "|" in data[1]:
58
obj.qual = obj.formats[data[1]]
59
else:
60
obj.qual = data[1]
61
obj.event.set()
62
63
64
class YtSelection:
65
def __init__(self, listener):
66
self.listener = listener
67
self._is_m4a = False
68
self._reply_to = None
69
self._time = time()
70
self._timeout = 120
71
self._is_playlist = False
72
self._main_buttons = None
73
self.event = Event()
74
self.formats = {}
75
self.qual = None
76
77
async def _event_handler(self):
78
pfunc = partial(select_format, obj=self)
79
handler = self.listener.client.add_handler(
80
CallbackQueryHandler(
81
pfunc, filters=regex("^ytq") & user(self.listener.user_id)
82
),
83
group=-1,
84
)
85
try:
86
await wait_for(self.event.wait(), timeout=self._timeout)
87
except:
88
await edit_message(self._reply_to, "Timed Out. Task has been cancelled!")
89
self.qual = None
90
self.listener.is_cancelled = True
91
self.event.set()
92
finally:
93
self.listener.client.remove_handler(*handler)
94
95
async def get_quality(self, result):
96
buttons = ButtonMaker()
97
if "entries" in result:
98
self._is_playlist = True
99
for i in ["144", "240", "360", "480", "720", "1080", "1440", "2160"]:
100
video_format = f"bv*[height<=?{i}][ext=mp4]+ba[ext=m4a]/b[height<=?{i}]"
101
b_data = f"{i}|mp4"
102
self.formats[b_data] = video_format
103
buttons.data_button(f"{i}-mp4", f"ytq {b_data}")
104
video_format = f"bv*[height<=?{i}][ext=webm]+ba/b[height<=?{i}]"
105
b_data = f"{i}|webm"
106
self.formats[b_data] = video_format
107
buttons.data_button(f"{i}-webm", f"ytq {b_data}")
108
buttons.data_button("MP3", "ytq mp3")
109
buttons.data_button("Audio Formats", "ytq audio")
110
buttons.data_button("Best Videos", "ytq bv*+ba/b")
111
buttons.data_button("Best Audios", "ytq ba/b")
112
buttons.data_button("Cancel", "ytq cancel", "footer")
113
self._main_buttons = buttons.build_menu(3)
114
msg = f"Choose Playlist Videos Quality:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"
115
else:
116
format_dict = result.get("formats")
117
if format_dict is not None:
118
for item in format_dict:
119
if item.get("tbr"):
120
format_id = item["format_id"]
121
122
if item.get("filesize"):
123
size = item["filesize"]
124
elif item.get("filesize_approx"):
125
size = item["filesize_approx"]
126
else:
127
size = 0
128
129
if item.get("video_ext") == "none" and (
130
item.get("resolution") == "audio only"
131
or item.get("acodec") != "none"
132
):
133
if item.get("audio_ext") == "m4a":
134
self._is_m4a = True
135
b_name = f"{item.get('acodec') or format_id}-{item['ext']}"
136
v_format = format_id
137
elif item.get("height"):
138
height = item["height"]
139
ext = item["ext"]
140
fps = item["fps"] if item.get("fps") else ""
141
b_name = f"{height}p{fps}-{ext}"
142
ba_ext = (
143
"[ext=m4a]" if self._is_m4a and ext == "mp4" else ""
144
)
145
v_format = f"{format_id}+ba{ba_ext}/b[height=?{height}]"
146
else:
147
continue
148
149
self.formats.setdefault(b_name, {})[f"{item['tbr']}"] = [
150
size,
151
v_format,
152
]
153
154
for b_name, tbr_dict in self.formats.items():
155
if len(tbr_dict) == 1:
156
tbr, v_list = next(iter(tbr_dict.items()))
157
buttonName = f"{b_name} ({get_readable_file_size(v_list[0])})"
158
buttons.data_button(buttonName, f"ytq sub {b_name} {tbr}")
159
else:
160
buttons.data_button(b_name, f"ytq dict {b_name}")
161
buttons.data_button("MP3", "ytq mp3")
162
buttons.data_button("Audio Formats", "ytq audio")
163
buttons.data_button("Best Video", "ytq bv*+ba/b")
164
buttons.data_button("Best Audio", "ytq ba/b")
165
buttons.data_button("Cancel", "ytq cancel", "footer")
166
self._main_buttons = buttons.build_menu(2)
167
msg = f"Choose Video Quality:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"
168
self._reply_to = await send_message(
169
self.listener.message, msg, self._main_buttons
170
)
171
await self._event_handler()
172
if not self.listener.is_cancelled:
173
await delete_message(self._reply_to)
174
return self.qual
175
176
async def back_to_main(self):
177
if self._is_playlist:
178
msg = f"Choose Playlist Videos Quality:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"
179
else:
180
msg = f"Choose Video Quality:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"
181
await edit_message(self._reply_to, msg, self._main_buttons)
182
183
async def qual_subbuttons(self, b_name):
184
buttons = ButtonMaker()
185
tbr_dict = self.formats[b_name]
186
for tbr, d_data in tbr_dict.items():
187
button_name = f"{tbr}K ({get_readable_file_size(d_data[0])})"
188
buttons.data_button(button_name, f"ytq sub {b_name} {tbr}")
189
buttons.data_button("Back", "ytq back", "footer")
190
buttons.data_button("Cancel", "ytq cancel", "footer")
191
subbuttons = buttons.build_menu(2)
192
msg = f"Choose Bit rate for <b>{b_name}</b>:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"
193
await edit_message(self._reply_to, msg, subbuttons)
194
195
async def mp3_subbuttons(self):
196
i = "s" if self._is_playlist else ""
197
buttons = ButtonMaker()
198
audio_qualities = [64, 128, 320]
199
for q in audio_qualities:
200
audio_format = f"ba/b-mp3-{q}"
201
buttons.data_button(f"{q}K-mp3", f"ytq {audio_format}")
202
buttons.data_button("Back", "ytq back")
203
buttons.data_button("Cancel", "ytq cancel")
204
subbuttons = buttons.build_menu(3)
205
msg = f"Choose mp3 Audio{i} Bitrate:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"
206
await edit_message(self._reply_to, msg, subbuttons)
207
208
async def audio_format(self):
209
i = "s" if self._is_playlist else ""
210
buttons = ButtonMaker()
211
for frmt in ["aac", "alac", "flac", "m4a", "opus", "vorbis", "wav"]:
212
audio_format = f"ba/b-{frmt}-"
213
buttons.data_button(frmt, f"ytq aq {audio_format}")
214
buttons.data_button("Back", "ytq back", "footer")
215
buttons.data_button("Cancel", "ytq cancel", "footer")
216
subbuttons = buttons.build_menu(3)
217
msg = f"Choose Audio{i} Format:\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"
218
await edit_message(self._reply_to, msg, subbuttons)
219
220
async def audio_quality(self, format):
221
i = "s" if self._is_playlist else ""
222
buttons = ButtonMaker()
223
for qual in range(11):
224
audio_format = f"{format}{qual}"
225
buttons.data_button(qual, f"ytq {audio_format}")
226
buttons.data_button("Back", "ytq aq back")
227
buttons.data_button("Cancel", "ytq aq cancel")
228
subbuttons = buttons.build_menu(5)
229
msg = f"Choose Audio{i} Quality:\n0 is best and 10 is worst\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}"
230
await edit_message(self._reply_to, msg, subbuttons)
231
232
233
def extract_info(link, options):
234
with YoutubeDL(options) as ydl:
235
result = ydl.extract_info(link, download=False)
236
if result is None:
237
raise ValueError("Info result is None")
238
return result
239
240
241
async def _mdisk(link, name):
242
key = link.split("/")[-1]
243
async with AsyncClient(verify=False) as client:
244
resp = await client.get(
245
f"https://diskuploader.entertainvideo.com/v1/file/cdnurl?param={key}"
246
)
247
if resp.status_code == 200:
248
resp_json = resp.json()
249
link = resp_json["source"]
250
if not name:
251
name = resp_json["filename"]
252
return name, link
253
254
255
class YtDlp(TaskListener):
256
def __init__(
257
self,
258
client,
259
message,
260
_=None,
261
is_leech=False,
262
__=None,
263
___=None,
264
same_dir=None,
265
bulk=None,
266
multi_tag=None,
267
options="",
268
):
269
if same_dir is None:
270
same_dir = {}
271
if bulk is None:
272
bulk = []
273
self.message = message
274
self.client = client
275
self.multi_tag = multi_tag
276
self.options = options
277
self.same_dir = same_dir
278
self.bulk = bulk
279
super().__init__()
280
self.is_ytdlp = True
281
self.is_leech = is_leech
282
283
async def new_event(self):
284
text = self.message.text.split("\n")
285
input_list = text[0].split(" ")
286
qual = ""
287
288
args = {
289
"-doc": False,
290
"-med": False,
291
"-s": False,
292
"-b": False,
293
"-z": False,
294
"-sv": False,
295
"-ss": False,
296
"-f": False,
297
"-fd": False,
298
"-fu": False,
299
"-hl": False,
300
"-bt": False,
301
"-ut": False,
302
"-i": 0,
303
"-sp": 0,
304
"link": "",
305
"-m": "",
306
"-opt": {},
307
"-n": "",
308
"-up": "",
309
"-rcf": "",
310
"-t": "",
311
"-ca": "",
312
"-cv": "",
313
"-ns": "",
314
"-tl": "",
315
"-ff": set(),
316
}
317
318
arg_parser(input_list[1:], args)
319
320
try:
321
self.multi = int(args["-i"])
322
except:
323
self.multi = 0
324
325
try:
326
opt = eval(args["-opt"]) if args["-opt"] else {}
327
except Exception as e:
328
LOGGER.error(e)
329
opt = {}
330
331
self.ffmpeg_cmds = args["-ff"]
332
self.select = args["-s"]
333
self.name = args["-n"]
334
self.up_dest = args["-up"]
335
self.rc_flags = args["-rcf"]
336
self.link = args["link"]
337
self.compress = args["-z"]
338
self.thumb = args["-t"]
339
self.split_size = args["-sp"]
340
self.sample_video = args["-sv"]
341
self.screen_shots = args["-ss"]
342
self.force_run = args["-f"]
343
self.force_download = args["-fd"]
344
self.force_upload = args["-fu"]
345
self.convert_audio = args["-ca"]
346
self.convert_video = args["-cv"]
347
self.name_sub = args["-ns"]
348
self.hybrid_leech = args["-hl"]
349
self.thumbnail_layout = args["-tl"]
350
self.as_doc = args["-doc"]
351
self.as_med = args["-med"]
352
self.folder_name = f"/{args["-m"]}".rstrip("/") if len(args["-m"]) > 0 else ""
353
self.bot_trans = args["-bt"]
354
self.user_trans = args["-ut"]
355
356
is_bulk = args["-b"]
357
358
bulk_start = 0
359
bulk_end = 0
360
reply_to = None
361
362
if not isinstance(is_bulk, bool):
363
dargs = is_bulk.split(":")
364
bulk_start = dargs[0] or None
365
if len(dargs) == 2:
366
bulk_end = dargs[1] or None
367
is_bulk = True
368
369
if not is_bulk:
370
if self.multi > 0:
371
if self.folder_name:
372
async with task_dict_lock:
373
if self.folder_name in self.same_dir:
374
self.same_dir[self.folder_name]["tasks"].add(self.mid)
375
for fd_name in self.same_dir:
376
if fd_name != self.folder_name:
377
self.same_dir[fd_name]["total"] -= 1
378
elif self.same_dir:
379
self.same_dir[self.folder_name] = {
380
"total": self.multi,
381
"tasks": {self.mid},
382
}
383
for fd_name in self.same_dir:
384
if fd_name != self.folder_name:
385
self.same_dir[fd_name]["total"] -= 1
386
else:
387
self.same_dir = {
388
self.folder_name: {
389
"total": self.multi,
390
"tasks": {self.mid},
391
}
392
}
393
elif self.same_dir:
394
async with task_dict_lock:
395
for fd_name in self.same_dir:
396
self.same_dir[fd_name]["total"] -= 1
397
else:
398
await self.init_bulk(input_list, bulk_start, bulk_end, YtDlp)
399
return
400
401
if len(self.bulk) != 0:
402
del self.bulk[0]
403
404
path = f"{DOWNLOAD_DIR}{self.mid}{self.folder_name}"
405
406
await self.get_tag(text)
407
408
opt = opt or self.user_dict.get("YT_DLP_OPTIONS") or Config.YT_DLP_OPTIONS
409
410
if not self.link and (reply_to := self.message.reply_to_message):
411
self.link = reply_to.text.split("\n", 1)[0].strip()
412
413
if not is_url(self.link):
414
await send_message(
415
self.message, COMMAND_USAGE["yt"][0], COMMAND_USAGE["yt"][1]
416
)
417
await self.remove_from_same_dir()
418
return
419
420
if "mdisk.me" in self.link:
421
self.name, self.link = await _mdisk(self.link, self.name)
422
423
try:
424
await self.before_start()
425
except Exception as e:
426
await send_message(self.message, e)
427
await self.remove_from_same_dir()
428
return
429
options = {"usenetrc": True, "cookiefile": "cookies.txt"}
430
if opt:
431
for key, value in opt.items():
432
if key in ["postprocessors", "download_ranges"]:
433
continue
434
if key == "format" and not self.select:
435
if value.startswith("ba/b-"):
436
qual = value
437
continue
438
else:
439
qual = value
440
options[key] = value
441
options["playlist_items"] = "0"
442
try:
443
result = await sync_to_async(extract_info, self.link, options)
444
except Exception as e:
445
msg = str(e).replace("<", " ").replace(">", " ")
446
await send_message(self.message, f"{self.tag} {msg}")
447
await self.remove_from_same_dir()
448
return
449
finally:
450
await self.run_multi(input_list, YtDlp)
451
452
if not qual:
453
qual = await YtSelection(self).get_quality(result)
454
if qual is None:
455
await self.remove_from_same_dir()
456
return
457
458
LOGGER.info(f"Downloading with YT-DLP: {self.link}")
459
playlist = "entries" in result
460
ydl = YoutubeDLHelper(self)
461
await ydl.add_download(path, qual, playlist, opt)
462
463
464
async def ytdl(client, message):
465
bot_loop.create_task(YtDlp(client, message).new_event())
466
467
468
async def ytdl_leech(client, message):
469
bot_loop.create_task(YtDlp(client, message, is_leech=True).new_event())
470
471