update
swadhinbiswas committed 2 years ago
e61a084
1 parent 2e0fc11
commit e61a084
14 files changed
+1085 -1
Showing 14 changed files+1085-1
app/bot/helper/ext_utils/dbhandelar.pyapp/bot/helper/ext_utils/exceptions.pyapp/bot/helper/ext_utils/files_utils.pyapp/bot/helper/ext_utils/link_utils.pyapp/bot/helper/ext_utils/media_utils.pyapp/bot/helper/ext_utils/taskmanager.pyapp/doodfunction/__init__.pyapp/doodfunction/upload.pyapp/main.pyapp/schemas/test.pyapp/setting/__init__.pyapp/setting/config.pyapp/settings/config.pysetup.py
▼app/bot/helper/ext_utils/dbhandelar.py
+1-1
| @@ -14,7 +14,7 @@ class DbManager: | ||
14 | 14 | |
15 | 15 | def connect(self): |
16 | 16 | try: |
17 | - self._conn = AsyncIOMotorClient(DATABASE_URL) | |
17 | + self._conn = AsyncIOMotorClient(MONGO_DB_URI) | |
18 | 18 | self._db = self._conn.mltb |
19 | 19 | except PyMongoError as e: |
20 | 20 | LOGGER.error(f"Error in DB connection: {e}") |
21 | 21 | |
▼app/bot/helper/ext_utils/exceptions.py
+22-0
| @@ -0,0 +1,22 @@ | ||
1 | +class DirectDownloadLinkException(Exception): | |
2 | + """Not method found for extracting direct download link from the http link""" | |
3 | + | |
4 | + pass | |
5 | + | |
6 | + | |
7 | +class NotSupportedExtractionArchive(Exception): | |
8 | + """The archive format use is trying to extract is not supported""" | |
9 | + | |
10 | + pass | |
11 | + | |
12 | + | |
13 | +class RssShutdownException(Exception): | |
14 | + """This exception should be raised when shutdown is called to stop the montior""" | |
15 | + | |
16 | + pass | |
17 | + | |
18 | + | |
19 | +class TgLinkException(Exception): | |
20 | + """No Access granted for this chat""" | |
21 | + | |
22 | + pass | |
0 | 23 | |
▼app/bot/helper/ext_utils/files_utils.py
+206-0
| @@ -0,0 +1,206 @@ | ||
1 | +from aiofiles.os import remove, path as aiopath, listdir, rmdir | |
2 | +from aioshutil import rmtree as aiormtree | |
3 | +from magic import Magic | |
4 | +from os import walk, path as ospath, makedirs | |
5 | +from re import split as re_split, I, search as re_search, escape | |
6 | +from shutil import rmtree | |
7 | +from subprocess import run as srun | |
8 | +from sys import exit as sexit | |
9 | + | |
10 | +from bot import aria2, LOGGER, DOWNLOAD_DIR, get_qb_client | |
11 | +from bot.helper.ext_utils.bot_utils import sync_to_async, cmd_exec | |
12 | +from .exceptions import NotSupportedExtractionArchive | |
13 | + | |
14 | +ARCH_EXT = [ | |
15 | + ".tar.bz2", | |
16 | + ".tar.gz", | |
17 | + ".bz2", | |
18 | + ".gz", | |
19 | + ".tar.xz", | |
20 | + ".tar", | |
21 | + ".tbz2", | |
22 | + ".tgz", | |
23 | + ".lzma2", | |
24 | + ".zip", | |
25 | + ".7z", | |
26 | + ".z", | |
27 | + ".rar", | |
28 | + ".iso", | |
29 | + ".wim", | |
30 | + ".cab", | |
31 | + ".apm", | |
32 | + ".arj", | |
33 | + ".chm", | |
34 | + ".cpio", | |
35 | + ".cramfs", | |
36 | + ".deb", | |
37 | + ".dmg", | |
38 | + ".fat", | |
39 | + ".hfs", | |
40 | + ".lzh", | |
41 | + ".lzma", | |
42 | + ".mbr", | |
43 | + ".msi", | |
44 | + ".mslz", | |
45 | + ".nsis", | |
46 | + ".ntfs", | |
47 | + ".rpm", | |
48 | + ".squashfs", | |
49 | + ".udf", | |
50 | + ".vhd", | |
51 | + ".xar", | |
52 | +] | |
53 | + | |
54 | +FIRST_SPLIT_REGEX = r"(\.|_)part0*1\.rar$|(\.|_)7z\.0*1$|(\.|_)zip\.0*1$|^(?!.*(\.|_)part\d+\.rar$).*\.rar$" | |
55 | + | |
56 | +SPLIT_REGEX = r"\.r\d+$|\.7z\.\d+$|\.z\d+$|\.zip\.\d+$" | |
57 | + | |
58 | + | |
59 | +def is_first_archive_split(file): | |
60 | + return bool(re_search(FIRST_SPLIT_REGEX, file)) | |
61 | + | |
62 | + | |
63 | +def is_archive(file): | |
64 | + return file.endswith(tuple(ARCH_EXT)) | |
65 | + | |
66 | + | |
67 | +def is_archive_split(file): | |
68 | + return bool(re_search(SPLIT_REGEX, file)) | |
69 | + | |
70 | + | |
71 | +async def clean_target(path): | |
72 | + if await aiopath.exists(path): | |
73 | + LOGGER.info(f"Cleaning Target: {path}") | |
74 | + try: | |
75 | + if await aiopath.isdir(path): | |
76 | + await aiormtree(path, ignore_errors=True) | |
77 | + else: | |
78 | + await remove(path) | |
79 | + except Exception as e: | |
80 | + LOGGER.error(str(e)) | |
81 | + | |
82 | + | |
83 | +async def clean_download(path): | |
84 | + if await aiopath.exists(path): | |
85 | + LOGGER.info(f"Cleaning Download: {path}") | |
86 | + try: | |
87 | + await aiormtree(path, ignore_errors=True) | |
88 | + except Exception as e: | |
89 | + LOGGER.error(str(e)) | |
90 | + | |
91 | + | |
92 | +def clean_all(): | |
93 | + aria2.remove_all(True) | |
94 | + get_qb_client().torrents_delete(torrent_hashes="all") | |
95 | + try: | |
96 | + LOGGER.info("Cleaning Download Directory...") | |
97 | + rmtree(DOWNLOAD_DIR, ignore_errors=True) | |
98 | + except: | |
99 | + pass | |
100 | + makedirs(DOWNLOAD_DIR, exist_ok=True) | |
101 | + | |
102 | + | |
103 | +def exit_clean_up(signal, frame): | |
104 | + try: | |
105 | + LOGGER.info("Please wait, while we clean up and stop the running downloads") | |
106 | + clean_all() | |
107 | + srun(["pkill", "-9", "-f", "gunicorn|aria2c|qbittorrent-nox|ffmpeg|java"]) | |
108 | + sexit(0) | |
109 | + except KeyboardInterrupt: | |
110 | + LOGGER.warning("Force Exiting before the cleanup finishes!") | |
111 | + sexit(1) | |
112 | + | |
113 | + | |
114 | +async def clean_unwanted(path, custom_list=None): | |
115 | + if custom_list is None: | |
116 | + custom_list = [] | |
117 | + LOGGER.info(f"Cleaning unwanted files/folders: {path}") | |
118 | + for dirpath, _, files in await sync_to_async(walk, path, topdown=False): | |
119 | + for filee in files: | |
120 | + f_path = ospath.join(dirpath, filee) | |
121 | + if ( | |
122 | + filee.endswith(".!qB") | |
123 | + or f_path in custom_list | |
124 | + or filee.endswith(".parts") | |
125 | + and filee.startswith(".") | |
126 | + ): | |
127 | + await remove(f_path) | |
128 | + if dirpath.endswith((".unwanted", "splited_files_mltb", "copied_mltb")): | |
129 | + await aiormtree(dirpath, ignore_errors=True) | |
130 | + for dirpath, _, files in await sync_to_async(walk, path, topdown=False): | |
131 | + if not await listdir(dirpath): | |
132 | + await rmdir(dirpath) | |
133 | + | |
134 | + | |
135 | +async def get_path_size(path): | |
136 | + if await aiopath.isfile(path): | |
137 | + return await aiopath.getsize(path) | |
138 | + total_size = 0 | |
139 | + for root, _, files in await sync_to_async(walk, path): | |
140 | + for f in files: | |
141 | + abs_path = ospath.join(root, f) | |
142 | + total_size += await aiopath.getsize(abs_path) | |
143 | + return total_size | |
144 | + | |
145 | + | |
146 | +async def count_files_and_folders(path, extension_filter, unwanted_files=None): | |
147 | + if unwanted_files is None: | |
148 | + unwanted_files = [] | |
149 | + total_files = 0 | |
150 | + total_folders = 0 | |
151 | + for dirpath, dirs, files in await sync_to_async(walk, path): | |
152 | + total_files += len(files) | |
153 | + for f in files: | |
154 | + if f.endswith(tuple(extension_filter)): | |
155 | + total_files -= 1 | |
156 | + elif unwanted_files: | |
157 | + f_path = ospath.join(dirpath, f) | |
158 | + if f_path in unwanted_files: | |
159 | + total_files -= 1 | |
160 | + total_folders += len(dirs) | |
161 | + return total_folders, total_files | |
162 | + | |
163 | + | |
164 | +def get_base_name(orig_path): | |
165 | + extension = next((ext for ext in ARCH_EXT if orig_path.lower().endswith(ext)), "") | |
166 | + if extension != "": | |
167 | + return re_split(f"{extension}$", orig_path, maxsplit=1, flags=I)[0] | |
168 | + else: | |
169 | + raise NotSupportedExtractionArchive("File format not supported for extraction") | |
170 | + | |
171 | + | |
172 | +def get_mime_type(file_path): | |
173 | + mime = Magic(mime=True) | |
174 | + mime_type = mime.from_file(file_path) | |
175 | + mime_type = mime_type or "text/plain" | |
176 | + return mime_type | |
177 | + | |
178 | + | |
179 | +async def join_files(path): | |
180 | + files = await listdir(path) | |
181 | + results = [] | |
182 | + exists = False | |
183 | + for file_ in files: | |
184 | + if re_search(r"\.0+2$", file_) and await sync_to_async( | |
185 | + get_mime_type, f"{path}/{file_}" | |
186 | + ) not in ["application/x-7z-compressed", "application/zip"]: | |
187 | + exists = True | |
188 | + final_name = file_.rsplit(".", 1)[0] | |
189 | + fpath = f"{path}/{final_name}" | |
190 | + cmd = f'cat "{fpath}."* > "{fpath}"' | |
191 | + _, stderr, code = await cmd_exec(cmd, True) | |
192 | + if code != 0: | |
193 | + LOGGER.error(f"Failed to join {final_name}, stderr: {stderr}") | |
194 | + if await aiopath.isfile(fpath): | |
195 | + await remove(fpath) | |
196 | + else: | |
197 | + results.append(final_name) | |
198 | + | |
199 | + if not exists: | |
200 | + LOGGER.warning("No files to join!") | |
201 | + elif results: | |
202 | + LOGGER.info("Join Completed!") | |
203 | + for res in results: | |
204 | + for file_ in files: | |
205 | + if re_search(rf"{escape(res)}\.0[0-9]+$", file_): | |
206 | + await remove(f"{path}/{file_}") | |
0 | 207 | |
▼app/bot/helper/ext_utils/link_utils.py
+53-0
| @@ -0,0 +1,53 @@ | ||
1 | +from re import match as re_match | |
2 | + | |
3 | + | |
4 | +def is_magnet(url: str): | |
5 | + return bool(re_match(r"magnet:\?xt=urn:(btih|btmh):[a-zA-Z0-9]*\s*", url)) | |
6 | + | |
7 | + | |
8 | +def is_url(url: str): | |
9 | + return bool( | |
10 | + re_match( | |
11 | + r"^(?!\/)(rtmps?:\/\/|mms:\/\/|rtsp:\/\/|https?:\/\/|ftp:\/\/)?([^\/:]+:[^\/@]+@)?(www\.)?(?=[^\/:\s]+\.[^\/:\s]+)([^\/:\s]+\.[^\/:\s]+)(:\d+)?(\/[^#\s]*[\s\S]*)?(\?[^#\s]*)?(#.*)?$", | |
12 | + url, | |
13 | + ) | |
14 | + ) | |
15 | + | |
16 | + | |
17 | +def is_gdrive_link(url: str): | |
18 | + return "drive.google.com" in url or "drive.usercontent.google.com" in url | |
19 | + | |
20 | + | |
21 | +def is_telegram_link(url: str): | |
22 | + return url.startswith(("https://t.me/", "tg://openmessage?user_id=")) | |
23 | + | |
24 | + | |
25 | +def is_share_link(url: str): | |
26 | + return bool( | |
27 | + re_match( | |
28 | + r"https?:\/\/.+\.gdtot\.\S+|https?:\/\/(filepress|filebee|appdrive|gdflix)\.\S+", | |
29 | + url, | |
30 | + ) | |
31 | + ) | |
32 | + | |
33 | + | |
34 | +def is_rclone_path(path: str): | |
35 | + return bool( | |
36 | + re_match( | |
37 | + r"^(mrcc:)?(?!(magnet:|mtp:|sa:|tp:))(?![- ])[a-zA-Z0-9_\. -]+(?<! ):(?!.*\/\/).*$|^rcl$", | |
38 | + path, | |
39 | + ) | |
40 | + ) | |
41 | + | |
42 | + | |
43 | +def is_gdrive_id(id_: str): | |
44 | + return bool( | |
45 | + re_match( | |
46 | + r"^(tp:|sa:|mtp:)?(?:[a-zA-Z0-9-_]{33}|[a-zA-Z0-9_-]{19})$|^gdl$|^(tp:|mtp:)?root$", | |
47 | + id_, | |
48 | + ) | |
49 | + ) | |
50 | + | |
51 | + | |
52 | +def get_mega_link_type(url: str): | |
53 | + return "folder" if "folder" in url or "/#F!" in url else "file" | |
0 | 54 | |
▼app/bot/helper/ext_utils/media_utils.py
+567-0
| @@ -0,0 +1,567 @@ | ||
1 | +from PIL import Image | |
2 | +from aiofiles.os import remove, path as aiopath, makedirs | |
3 | +from asyncio import create_subprocess_exec, gather, wait_for | |
4 | +from asyncio.subprocess import PIPE | |
5 | +from os import path as ospath, cpu_count | |
6 | +from re import search as re_search | |
7 | +from time import time | |
8 | +from aioshutil import rmtree | |
9 | + | |
10 | +from bot import LOGGER, subprocess_lock | |
11 | +from bot.helper.ext_utils.bot_utils import cmd_exec | |
12 | +from bot.helper.ext_utils.bot_utils import sync_to_async | |
13 | +from bot.helper.ext_utils.files_utils import ARCH_EXT, get_mime_type | |
14 | + | |
15 | + | |
16 | +async def convert_video(listener, video_file, ext, retry=False): | |
17 | + base_name = ospath.splitext(video_file)[0] | |
18 | + output = f"{base_name}.{ext}" | |
19 | + if retry: | |
20 | + cmd = [ | |
21 | + "ffmpeg", | |
22 | + "-i", | |
23 | + video_file, | |
24 | + "-c:v", | |
25 | + "libx264", | |
26 | + "-c:a", | |
27 | + "aac", | |
28 | + "-threads", | |
29 | + f"{cpu_count() // 2}", | |
30 | + output, | |
31 | + ] | |
32 | + if ext == "mp4": | |
33 | + cmd[7:7] = ["-c:s", "mov_text"] | |
34 | + elif ext == "mkv": | |
35 | + cmd[7:7] = ["-c:s", "ass"] | |
36 | + else: | |
37 | + cmd[7:7] = ["-c:s", "copy"] | |
38 | + else: | |
39 | + cmd = ["ffmpeg", "-i", video_file, "-map", "0", "-c", "copy", output] | |
40 | + if listener.isCancelled: | |
41 | + return False | |
42 | + listener.suproc = await create_subprocess_exec(*cmd, stderr=PIPE) | |
43 | + _, stderr = await listener.suproc.communicate() | |
44 | + if listener.isCancelled: | |
45 | + return False | |
46 | + code = listener.suproc.returncode | |
47 | + if code == 0: | |
48 | + return output | |
49 | + elif code == -9: | |
50 | + listener.isCancelled = True | |
51 | + return False | |
52 | + else: | |
53 | + if not retry: | |
54 | + if await aiopath.exists(output): | |
55 | + await remove(output) | |
56 | + return await convert_video(listener, video_file, ext, True) | |
57 | + else: | |
58 | + try: | |
59 | + stderr = stderr.decode().strip() | |
60 | + except: | |
61 | + stderr = "Unable to decode the error!" | |
62 | + LOGGER.error( | |
63 | + f"{stderr}. Something went wrong while converting video, mostly file need specific codec. Path: {video_file}" | |
64 | + ) | |
65 | + return False | |
66 | + | |
67 | + | |
68 | +async def convert_audio(listener, audio_file, ext): | |
69 | + base_name = ospath.splitext(audio_file)[0] | |
70 | + output = f"{base_name}.{ext}" | |
71 | + cmd = [ | |
72 | + "ffmpeg", | |
73 | + "-i", | |
74 | + audio_file, | |
75 | + "-threads", | |
76 | + f"{cpu_count() // 2}", | |
77 | + output, | |
78 | + ] | |
79 | + if listener.isCancelled: | |
80 | + return False | |
81 | + listener.suproc = await create_subprocess_exec(*cmd, stderr=PIPE) | |
82 | + _, stderr = await listener.suproc.communicate() | |
83 | + if listener.isCancelled: | |
84 | + return False | |
85 | + code = listener.suproc.returncode | |
86 | + if code == 0: | |
87 | + return output | |
88 | + elif code == -9: | |
89 | + listener.isCancelled = True | |
90 | + return False | |
91 | + else: | |
92 | + try: | |
93 | + stderr = stderr.decode().strip() | |
94 | + except: | |
95 | + stderr = "Unable to decode the error!" | |
96 | + LOGGER.error( | |
97 | + f"{stderr}. Something went wrong while converting audio, mostly file need specific codec. Path: {audio_file}" | |
98 | + ) | |
99 | + if await aiopath.exists(output): | |
100 | + await remove(output) | |
101 | + return False | |
102 | + | |
103 | + | |
104 | +async def createThumb(msg, _id=""): | |
105 | + if not _id: | |
106 | + _id = msg.id | |
107 | + path = "Thumbnails/" | |
108 | + await makedirs(path, exist_ok=True) | |
109 | + photo_dir = await msg.download() | |
110 | + des_dir = f"{path}{_id}.jpg" | |
111 | + await sync_to_async(Image.open(photo_dir).convert("RGB").save, des_dir, "JPEG") | |
112 | + await remove(photo_dir) | |
113 | + return des_dir | |
114 | + | |
115 | + | |
116 | +async def is_multi_streams(path): | |
117 | + try: | |
118 | + result = await cmd_exec( | |
119 | + [ | |
120 | + "ffprobe", | |
121 | + "-hide_banner", | |
122 | + "-loglevel", | |
123 | + "error", | |
124 | + "-print_format", | |
125 | + "json", | |
126 | + "-show_streams", | |
127 | + path, | |
128 | + ] | |
129 | + ) | |
130 | + except Exception as e: | |
131 | + LOGGER.error(f"Get Video Streams: {e}. Mostly File not found! - File: {path}") | |
132 | + return False | |
133 | + if result[0] and result[2] == 0: | |
134 | + fields = eval(result[0]).get("streams") | |
135 | + if fields is None: | |
136 | + LOGGER.error(f"get_video_streams: {result}") | |
137 | + return False | |
138 | + videos = 0 | |
139 | + audios = 0 | |
140 | + for stream in fields: | |
141 | + if stream.get("codec_type") == "video": | |
142 | + videos += 1 | |
143 | + elif stream.get("codec_type") == "audio": | |
144 | + audios += 1 | |
145 | + return videos > 1 or audios > 1 | |
146 | + return False | |
147 | + | |
148 | + | |
149 | +async def get_media_info(path): | |
150 | + try: | |
151 | + result = await cmd_exec( | |
152 | + [ | |
153 | + "ffprobe", | |
154 | + "-hide_banner", | |
155 | + "-loglevel", | |
156 | + "error", | |
157 | + "-print_format", | |
158 | + "json", | |
159 | + "-show_format", | |
160 | + path, | |
161 | + ] | |
162 | + ) | |
163 | + except Exception as e: | |
164 | + LOGGER.error(f"Get Media Info: {e}. Mostly File not found! - File: {path}") | |
165 | + return 0, None, None | |
166 | + if result[0] and result[2] == 0: | |
167 | + fields = eval(result[0]).get("format") | |
168 | + if fields is None: | |
169 | + LOGGER.error(f"get_media_info: {result}") | |
170 | + return 0, None, None | |
171 | + duration = round(float(fields.get("duration", 0))) | |
172 | + tags = fields.get("tags", {}) | |
173 | + artist = tags.get("artist") or tags.get("ARTIST") or tags.get("Artist") | |
174 | + title = tags.get("title") or tags.get("TITLE") or tags.get("Title") | |
175 | + return duration, artist, title | |
176 | + return 0, None, None | |
177 | + | |
178 | + | |
179 | +async def get_document_type(path): | |
180 | + is_video, is_audio, is_image = False, False, False | |
181 | + if path.endswith(tuple(ARCH_EXT)) or re_search( | |
182 | + r".+(\.|_)(rar|7z|zip|bin)(\.0*\d+)?$", path | |
183 | + ): | |
184 | + return is_video, is_audio, is_image | |
185 | + mime_type = await sync_to_async(get_mime_type, path) | |
186 | + if mime_type.startswith("image"): | |
187 | + return False, False, True | |
188 | + if mime_type.startswith("audio"): | |
189 | + return False, True, False | |
190 | + if not mime_type.startswith("video") and not mime_type.endswith("octet-stream"): | |
191 | + return is_video, is_audio, is_image | |
192 | + try: | |
193 | + result = await cmd_exec( | |
194 | + [ | |
195 | + "ffprobe", | |
196 | + "-hide_banner", | |
197 | + "-loglevel", | |
198 | + "error", | |
199 | + "-print_format", | |
200 | + "json", | |
201 | + "-show_streams", | |
202 | + path, | |
203 | + ] | |
204 | + ) | |
205 | + if res := result[1]: | |
206 | + if mime_type.startswith("video"): | |
207 | + is_video = True | |
208 | + except Exception as e: | |
209 | + LOGGER.error(f"Get Document Type: {e}. Mostly File not found! - File: {path}") | |
210 | + if mime_type.startswith("video"): | |
211 | + is_video = True | |
212 | + return is_video, is_audio, is_image | |
213 | + if result[0] and result[2] == 0: | |
214 | + fields = eval(result[0]).get("streams") | |
215 | + if fields is None: | |
216 | + LOGGER.error(f"get_document_type: {result}") | |
217 | + return is_video, is_audio, is_image | |
218 | + is_video = False | |
219 | + for stream in fields: | |
220 | + if stream.get("codec_type") == "video": | |
221 | + is_video = True | |
222 | + elif stream.get("codec_type") == "audio": | |
223 | + is_audio = True | |
224 | + return is_video, is_audio, is_image | |
225 | + | |
226 | + | |
227 | +async def take_ss(video_file, ss_nb) -> bool: | |
228 | + ss_nb = min(ss_nb, 10) | |
229 | + duration = (await get_media_info(video_file))[0] | |
230 | + if duration != 0: | |
231 | + dirpath, name = video_file.rsplit("/", 1) | |
232 | + name, _ = ospath.splitext(name) | |
233 | + dirpath = f"{dirpath}/{name}_mltbss/" | |
234 | + await makedirs(dirpath, exist_ok=True) | |
235 | + interval = duration // (ss_nb + 1) | |
236 | + cap_time = interval | |
237 | + cmds = [] | |
238 | + for i in range(ss_nb): | |
239 | + output = f"{dirpath}SS.{name}_{i:02}.png" | |
240 | + cmd = [ | |
241 | + "ffmpeg", | |
242 | + "-hide_banner", | |
243 | + "-loglevel", | |
244 | + "error", | |
245 | + "-ss", | |
246 | + f"{cap_time}", | |
247 | + "-i", | |
248 | + video_file, | |
249 | + "-q:v", | |
250 | + "1", | |
251 | + "-frames:v", | |
252 | + "1", | |
253 | + output, | |
254 | + ] | |
255 | + cap_time += interval | |
256 | + cmds.append(cmd_exec(cmd)) | |
257 | + try: | |
258 | + resutls = await wait_for(gather(*cmds), timeout=60) | |
259 | + if resutls[0][2] != 0: | |
260 | + LOGGER.error( | |
261 | + f"Error while creating sreenshots from video. Path: {video_file}. stderr: {resutls[0][1]}" | |
262 | + ) | |
263 | + await rmtree(dirpath, ignore_errors=True) | |
264 | + return False | |
265 | + except: | |
266 | + LOGGER.error( | |
267 | + f"Error while creating sreenshots from video. Path: {video_file}. Error: Timeout some issues with ffmpeg with specific arch!" | |
268 | + ) | |
269 | + await rmtree(dirpath, ignore_errors=True) | |
270 | + return False | |
271 | + return dirpath | |
272 | + else: | |
273 | + LOGGER.error("take_ss: Can't get the duration of video") | |
274 | + await rmtree(dirpath, ignore_errors=True) | |
275 | + return False | |
276 | + | |
277 | + | |
278 | +async def get_audio_thumb(audio_file): | |
279 | + des_dir = "Thumbnails/" | |
280 | + await makedirs(des_dir, exist_ok=True) | |
281 | + des_dir = f"Thumbnails/{time()}.jpg" | |
282 | + cmd = [ | |
283 | + "ffmpeg", | |
284 | + "-hide_banner", | |
285 | + "-loglevel", | |
286 | + "error", | |
287 | + "-i", | |
288 | + audio_file, | |
289 | + "-an", | |
290 | + "-vcodec", | |
291 | + "copy", | |
292 | + des_dir, | |
293 | + ] | |
294 | + _, err, code = await cmd_exec(cmd) | |
295 | + if code != 0 or not await aiopath.exists(des_dir): | |
296 | + LOGGER.error( | |
297 | + f"Error while extracting thumbnail from audio. Name: {audio_file} stderr: {err}" | |
298 | + ) | |
299 | + return None | |
300 | + return des_dir | |
301 | + | |
302 | + | |
303 | +async def create_thumbnail(video_file, duration): | |
304 | + des_dir = "Thumbnails" | |
305 | + await makedirs(des_dir, exist_ok=True) | |
306 | + des_dir = ospath.join(des_dir, f"{time()}.jpg") | |
307 | + if duration is None: | |
308 | + duration = (await get_media_info(video_file))[0] | |
309 | + if duration == 0: | |
310 | + duration = 3 | |
311 | + duration = duration // 2 | |
312 | + cmd = [ | |
313 | + "ffmpeg", | |
314 | + "-hide_banner", | |
315 | + "-loglevel", | |
316 | + "error", | |
317 | + "-ss", | |
318 | + f"{duration}", | |
319 | + "-i", | |
320 | + video_file, | |
321 | + "-vf", | |
322 | + "thumbnail", | |
323 | + "-frames:v", | |
324 | + "1", | |
325 | + des_dir, | |
326 | + ] | |
327 | + try: | |
328 | + _, err, code = await wait_for(cmd_exec(cmd), timeout=60) | |
329 | + if code != 0 or not await aiopath.exists(des_dir): | |
330 | + LOGGER.error( | |
331 | + f"Error while extracting thumbnail from video. Name: {video_file} stderr: {err}" | |
332 | + ) | |
333 | + return None | |
334 | + except: | |
335 | + LOGGER.error( | |
336 | + f"Error while extracting thumbnail from video. Name: {video_file}. Error: Timeout some issues with ffmpeg with specific arch!" | |
337 | + ) | |
338 | + return None | |
339 | + return des_dir | |
340 | + | |
341 | + | |
342 | +async def split_file( | |
343 | + path, | |
344 | + size, | |
345 | + dirpath, | |
346 | + file_, | |
347 | + split_size, | |
348 | + listener, | |
349 | + start_time=0, | |
350 | + i=1, | |
351 | + inLoop=False, | |
352 | + multi_streams=True, | |
353 | +): | |
354 | + if listener.seed and not listener.newDir: | |
355 | + dirpath = f"{dirpath}/splited_files_mltb" | |
356 | + await makedirs(dirpath, exist_ok=True) | |
357 | + parts = -(-size // listener.splitSize) | |
358 | + if listener.equalSplits and not inLoop: | |
359 | + split_size = (size // parts) + (size % parts) | |
360 | + if not listener.asDoc and (await get_document_type(path))[0]: | |
361 | + if multi_streams: | |
362 | + multi_streams = await is_multi_streams(path) | |
363 | + duration = (await get_media_info(path))[0] | |
364 | + base_name, extension = ospath.splitext(file_) | |
365 | + split_size -= 5000000 | |
366 | + while i <= parts or start_time < duration - 4: | |
367 | + out_path = f"{dirpath}/{base_name}.part{i:03}{extension}" | |
368 | + cmd = [ | |
369 | + "ffmpeg", | |
370 | + "-hide_banner", | |
371 | + "-loglevel", | |
372 | + "error", | |
373 | + "-ss", | |
374 | + str(start_time), | |
375 | + "-i", | |
376 | + path, | |
377 | + "-fs", | |
378 | + str(split_size), | |
379 | + "-map", | |
380 | + "0", | |
381 | + "-map_chapters", | |
382 | + "-1", | |
383 | + "-async", | |
384 | + "1", | |
385 | + "-strict", | |
386 | + "-2", | |
387 | + "-c", | |
388 | + "copy", | |
389 | + out_path, | |
390 | + ] | |
391 | + if not multi_streams: | |
392 | + del cmd[10] | |
393 | + del cmd[10] | |
394 | + if listener.isCancelled: | |
395 | + return False | |
396 | + async with subprocess_lock: | |
397 | + listener.suproc = await create_subprocess_exec(*cmd, stderr=PIPE) | |
398 | + _, stderr = await listener.suproc.communicate() | |
399 | + if listener.isCancelled: | |
400 | + return False | |
401 | + code = listener.suproc.returncode | |
402 | + if code == -9: | |
403 | + listener.isCancelled = True | |
404 | + return False | |
405 | + elif code != 0: | |
406 | + try: | |
407 | + stderr = stderr.decode().strip() | |
408 | + except: | |
409 | + stderr = "Unable to decode the error!" | |
410 | + try: | |
411 | + await remove(out_path) | |
412 | + except: | |
413 | + pass | |
414 | + if multi_streams: | |
415 | + LOGGER.warning( | |
416 | + f"{stderr}. Retrying without map, -map 0 not working in all situations. Path: {path}" | |
417 | + ) | |
418 | + return await split_file( | |
419 | + path, | |
420 | + size, | |
421 | + dirpath, | |
422 | + file_, | |
423 | + split_size, | |
424 | + listener, | |
425 | + start_time, | |
426 | + i, | |
427 | + True, | |
428 | + False, | |
429 | + ) | |
430 | + else: | |
431 | + LOGGER.warning( | |
432 | + f"{stderr}. Unable to split this video, if it's size less than {listener.maxSplitSize} will be uploaded as it is. Path: {path}" | |
433 | + ) | |
434 | + return False | |
435 | + out_size = await aiopath.getsize(out_path) | |
436 | + if out_size > listener.maxSplitSize: | |
437 | + dif = out_size - listener.maxSplitSize | |
438 | + split_size -= dif + 5000000 | |
439 | + await remove(out_path) | |
440 | + return await split_file( | |
441 | + path, | |
442 | + size, | |
443 | + dirpath, | |
444 | + file_, | |
445 | + split_size, | |
446 | + listener, | |
447 | + start_time, | |
448 | + i, | |
449 | + True, | |
450 | + multi_streams, | |
451 | + ) | |
452 | + lpd = (await get_media_info(out_path))[0] | |
453 | + if lpd == 0: | |
454 | + LOGGER.error( | |
455 | + f"Something went wrong while splitting, mostly file is corrupted. Path: {path}" | |
456 | + ) | |
457 | + break | |
458 | + elif duration == lpd: | |
459 | + LOGGER.warning( | |
460 | + f"This file has been splitted with default stream and audio, so you will only see one part with less size from orginal one because it doesn't have all streams and audios. This happens mostly with MKV videos. Path: {path}" | |
461 | + ) | |
462 | + break | |
463 | + elif lpd <= 3: | |
464 | + await remove(out_path) | |
465 | + break | |
466 | + start_time += lpd - 3 | |
467 | + i += 1 | |
468 | + else: | |
469 | + out_path = f"{dirpath}/{file_}." | |
470 | + async with subprocess_lock: | |
471 | + if listener.isCancelled: | |
472 | + return False | |
473 | + listener.suproc = await create_subprocess_exec( | |
474 | + "split", | |
475 | + "--numeric-suffixes=1", | |
476 | + "--suffix-length=3", | |
477 | + f"--bytes={split_size}", | |
478 | + path, | |
479 | + out_path, | |
480 | + stderr=PIPE, | |
481 | + ) | |
482 | + _, stderr = await listener.suproc.communicate() | |
483 | + if listener.isCancelled: | |
484 | + return False | |
485 | + code = listener.suproc.returncode | |
486 | + if code == -9: | |
487 | + listener.isCancelled = True | |
488 | + return False | |
489 | + elif code != 0: | |
490 | + try: | |
491 | + stderr = stderr.decode().strip() | |
492 | + except: | |
493 | + stderr = "Unable to decode the error!" | |
494 | + LOGGER.error(f"{stderr}. Split Document: {path}") | |
495 | + return True | |
496 | + | |
497 | + | |
498 | +async def createSampleVideo(listener, video_file, sample_duration, part_duration): | |
499 | + filter_complex = "" | |
500 | + dir, name = video_file.rsplit("/", 1) | |
501 | + output_file = f"{dir}/SAMPLE.{name}" | |
502 | + segments = [(0, part_duration)] | |
503 | + duration = (await get_media_info(video_file))[0] | |
504 | + remaining_duration = duration - (part_duration * 2) | |
505 | + parts = (sample_duration - (part_duration * 2)) // part_duration | |
506 | + time_interval = remaining_duration // parts | |
507 | + next_segment = time_interval | |
508 | + for _ in range(parts): | |
509 | + segments.append((next_segment, next_segment + part_duration)) | |
510 | + next_segment += time_interval | |
511 | + segments.append((duration - part_duration, duration)) | |
512 | + | |
513 | + for i, (start, end) in enumerate(segments): | |
514 | + filter_complex += ( | |
515 | + f"[0:v]trim=start={start}:end={end},setpts=PTS-STARTPTS[v{i}]; " | |
516 | + ) | |
517 | + filter_complex += ( | |
518 | + f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[a{i}]; " | |
519 | + ) | |
520 | + | |
521 | + for i in range(len(segments)): | |
522 | + filter_complex += f"[v{i}][a{i}]" | |
523 | + | |
524 | + filter_complex += f"concat=n={len(segments)}:v=1:a=1[vout][aout]" | |
525 | + | |
526 | + cmd = [ | |
527 | + "ffmpeg", | |
528 | + "-i", | |
529 | + video_file, | |
530 | + "-filter_complex", | |
531 | + filter_complex, | |
532 | + "-map", | |
533 | + "[vout]", | |
534 | + "-map", | |
535 | + "[aout]", | |
536 | + "-c:v", | |
537 | + "libx264", | |
538 | + "-c:a", | |
539 | + "aac", | |
540 | + "-threads", | |
541 | + f"{cpu_count() // 2}", | |
542 | + output_file, | |
543 | + ] | |
544 | + | |
545 | + if listener.isCancelled: | |
546 | + return False | |
547 | + listener.suproc = await create_subprocess_exec(*cmd, stderr=PIPE) | |
548 | + _, stderr = await listener.suproc.communicate() | |
549 | + if listener.isCancelled: | |
550 | + return False | |
551 | + code = listener.suproc.returncode | |
552 | + if code == -9: | |
553 | + listener.isCancelled = True | |
554 | + return False | |
555 | + elif code == 0: | |
556 | + return output_file | |
557 | + else: | |
558 | + try: | |
559 | + stderr = stderr.decode().strip() | |
560 | + except: | |
561 | + stderr = "Unable to decode the error!" | |
562 | + LOGGER.error( | |
563 | + f"{stderr}. Something went wrong while creating sample video, mostly file is corrupted. Path: {video_file}" | |
564 | + ) | |
565 | + if await aiopath.exists(output_file): | |
566 | + await remove(output_file) | |
567 | + return False | |
0 | 568 | |
▼app/bot/helper/ext_utils/taskmanager.py
+162-0
| @@ -0,0 +1,162 @@ | ||
1 | +from asyncio import Event, sleep | |
2 | + | |
3 | +from bot import ( | |
4 | + config_dict, | |
5 | + queued_dl, | |
6 | + queued_up, | |
7 | + non_queued_up, | |
8 | + non_queued_dl, | |
9 | + queue_dict_lock, | |
10 | + LOGGER, | |
11 | +) | |
12 | +from bot.helper.ext_utils.bot_utils import ( | |
13 | + sync_to_async, | |
14 | + get_telegraph_list, | |
15 | +) | |
16 | +from bot.helper.ext_utils.files_utils import get_base_name | |
17 | +from bot.helper.ext_utils.links_utils import is_gdrive_id | |
18 | +from bot.helper.mirror_leech_utils.gdrive_utils.search import gdSearch | |
19 | + | |
20 | + | |
21 | +async def stop_duplicate_check(listener): | |
22 | + if ( | |
23 | + isinstance(listener.upDest, int) | |
24 | + or listener.isLeech | |
25 | + or listener.select | |
26 | + or not is_gdrive_id(listener.upDest) | |
27 | + or (listener.upDest.startswith("mtp:") and listener.stopDuplicate) | |
28 | + or not listener.stopDuplicate | |
29 | + or listener.sameDir | |
30 | + ): | |
31 | + return False, None | |
32 | + | |
33 | + name = listener.name | |
34 | + LOGGER.info(f"Checking File/Folder if already in Drive: {name}") | |
35 | + | |
36 | + if listener.compress: | |
37 | + name = f"{name}.zip" | |
38 | + elif listener.extract: | |
39 | + try: | |
40 | + name = get_base_name(name) | |
41 | + except: | |
42 | + name = None | |
43 | + | |
44 | + if name is not None: | |
45 | + telegraph_content, contents_no = await sync_to_async( | |
46 | + gdSearch(stopDup=True, noMulti=listener.isClone).drive_list, | |
47 | + name, | |
48 | + listener.upDest, | |
49 | + listener.userId, | |
50 | + ) | |
51 | + if telegraph_content: | |
52 | + msg = f"File/Folder is already available in Drive.\nHere are {contents_no} list results:" | |
53 | + button = await get_telegraph_list(telegraph_content) | |
54 | + return msg, button | |
55 | + | |
56 | + return False, None | |
57 | + | |
58 | + | |
59 | +async def check_running_tasks(listener, state="dl"): | |
60 | + all_limit = config_dict["QUEUE_ALL"] | |
61 | + state_limit = ( | |
62 | + config_dict["QUEUE_DOWNLOAD"] if state == "dl" else config_dict["QUEUE_UPLOAD"] | |
63 | + ) | |
64 | + event = None | |
65 | + is_over_limit = False | |
66 | + async with queue_dict_lock: | |
67 | + if state == "up" and listener.mid in non_queued_dl: | |
68 | + non_queued_dl.remove(listener.mid) | |
69 | + if ( | |
70 | + (all_limit or state_limit) | |
71 | + and not listener.forceRun | |
72 | + and not (listener.forceUpload and state == "up") | |
73 | + and not (listener.forceDownload and state == "dl") | |
74 | + ): | |
75 | + dl_count = len(non_queued_dl) | |
76 | + up_count = len(non_queued_up) | |
77 | + t_count = dl_count if state == "dl" else up_count | |
78 | + is_over_limit = ( | |
79 | + all_limit | |
80 | + and dl_count + up_count >= all_limit | |
81 | + and (not state_limit or t_count >= state_limit) | |
82 | + ) or (state_limit and t_count >= state_limit) | |
83 | + if is_over_limit: | |
84 | + event = Event() | |
85 | + if state == "dl": | |
86 | + queued_dl[listener.mid] = event | |
87 | + else: | |
88 | + queued_up[listener.mid] = event | |
89 | + if not is_over_limit: | |
90 | + if state == "up": | |
91 | + non_queued_up.add(listener.mid) | |
92 | + else: | |
93 | + non_queued_dl.add(listener.mid) | |
94 | + | |
95 | + return is_over_limit, event | |
96 | + | |
97 | + | |
98 | +async def start_dl_from_queued(mid: int): | |
99 | + queued_dl[mid].set() | |
100 | + del queued_dl[mid] | |
101 | + await sleep(0.7) | |
102 | + | |
103 | + | |
104 | +async def start_up_from_queued(mid: int): | |
105 | + queued_up[mid].set() | |
106 | + del queued_up[mid] | |
107 | + await sleep(0.7) | |
108 | + | |
109 | + | |
110 | +async def start_from_queued(): | |
111 | + if all_limit := config_dict["QUEUE_ALL"]: | |
112 | + dl_limit = config_dict["QUEUE_DOWNLOAD"] | |
113 | + up_limit = config_dict["QUEUE_UPLOAD"] | |
114 | + async with queue_dict_lock: | |
115 | + dl = len(non_queued_dl) | |
116 | + up = len(non_queued_up) | |
117 | + all_ = dl + up | |
118 | + if all_ < all_limit: | |
119 | + f_tasks = all_limit - all_ | |
120 | + if queued_up and (not up_limit or up < up_limit): | |
121 | + for index, mid in enumerate(list(queued_up.keys()), start=1): | |
122 | + f_tasks = all_limit - all_ | |
123 | + await start_up_from_queued(mid) | |
124 | + f_tasks -= 1 | |
125 | + if f_tasks == 0 or (up_limit and index >= up_limit - up): | |
126 | + break | |
127 | + if queued_dl and (not dl_limit or dl < dl_limit) and f_tasks != 0: | |
128 | + for index, mid in enumerate(list(queued_dl.keys()), start=1): | |
129 | + await start_dl_from_queued(mid) | |
130 | + if (dl_limit and index >= dl_limit - dl) or index == f_tasks: | |
131 | + break | |
132 | + return | |
133 | + | |
134 | + if up_limit := config_dict["QUEUE_UPLOAD"]: | |
135 | + async with queue_dict_lock: | |
136 | + up = len(non_queued_up) | |
137 | + if queued_up and up < up_limit: | |
138 | + f_tasks = up_limit - up | |
139 | + for index, mid in enumerate(list(queued_up.keys()), start=1): | |
140 | + await start_up_from_queued(mid) | |
141 | + if index == f_tasks: | |
142 | + break | |
143 | + else: | |
144 | + async with queue_dict_lock: | |
145 | + if queued_up: | |
146 | + for mid in list(queued_up.keys()): | |
147 | + await start_up_from_queued(mid) | |
148 | + | |
149 | + if dl_limit := config_dict["QUEUE_DOWNLOAD"]: | |
150 | + async with queue_dict_lock: | |
151 | + dl = len(non_queued_dl) | |
152 | + if queued_dl and dl < dl_limit: | |
153 | + f_tasks = dl_limit - dl | |
154 | + for index, mid in enumerate(list(queued_dl.keys()), start=1): | |
155 | + await start_dl_from_queued(mid) | |
156 | + if index == f_tasks: | |
157 | + break | |
158 | + else: | |
159 | + async with queue_dict_lock: | |
160 | + if queued_dl: | |
161 | + for mid in list(queued_dl.keys()): | |
162 | + await start_dl_from_queued(mid) | |
0 | 163 | |
▼app/__init__.py → app/doodfunction/__init__.py
+0-0
0 | 0 |
▼app/doodfunction/upload.py
+23-0
| @@ -0,0 +1,23 @@ | ||
1 | +from settings import config as config | |
2 | +# ,STREAMTAPEAPIKEY,STREAMTAPEUSERAGENT | |
3 | +import requests | |
4 | +from fastapi import APIRouter, File, UploadFile, Form,FastAPI | |
5 | +from fastapi.responses import JSONResponse | |
6 | +from fastapi.encoders import jsonable_encoder | |
7 | + | |
8 | +app=FastAPI() | |
9 | +router = APIRouter() | |
10 | + | |
11 | +@app.get("/userinfo") | |
12 | +async def userinfo(): | |
13 | + url=f"https://doodapi.com/api/account/info?key=383661fid15w8jz8w50qza" | |
14 | + response = requests.get(url) | |
15 | + return response.json() | |
16 | + | |
17 | + | |
18 | + | |
19 | + | |
20 | + | |
21 | +if __name__ == "__main__": | |
22 | + import uvicorn | |
23 | + uvicorn.run(app, host="localhost", port=8000) | |
0 | 24 | |
▼app/main.py
+23-0
| @@ -0,0 +1,23 @@ | ||
1 | +from app import setting | |
2 | +# ,STREAMTAPEAPIKEY,STREAMTAPEUSERAGENT | |
3 | +import requests | |
4 | +from fastapi import APIRouter, File, UploadFile, Form,FastAPI | |
5 | +from fastapi.responses import JSONResponse | |
6 | +from fastapi.encoders import jsonable_encoder | |
7 | + | |
8 | +app=FastAPI() | |
9 | +router = APIRouter() | |
10 | + | |
11 | +@app.get("userinfo") | |
12 | +async def userinfo(): | |
13 | + url=f"https://doodapi.com/api/account/info?key={DOODAPIKEY}" | |
14 | + response = requests.get(url) | |
15 | + return response.json() | |
16 | + | |
17 | + | |
18 | + | |
19 | + | |
20 | + | |
21 | +if __name__ == "__main__": | |
22 | + import uvicorn | |
23 | + uvicorn.run(app, host="localhost", port=8000) | |
0 | 24 | |
▼app/schemas/test.py
+2-0
| @@ -0,0 +1,2 @@ | ||
1 | +def printx(): | |
2 | + print("Hello World!") | |
0 | 3 | |
▼app/settings/__init__.py → app/setting/__init__.py
+0-0
0 | 0 |
▼app/setting/config.py
+5-0
| @@ -0,0 +1,5 @@ | ||
1 | +DOODAPIKEY="383661fid15w8jz8w50qza" | |
2 | + | |
3 | +from app.schemas.test import printx | |
4 | + | |
5 | +printx() | |
0 | 6 | |
▼app/settings/config.py
+0-0
0 | 0 |
▼setup.py
+21-0
| @@ -0,0 +1,21 @@ | ||
1 | +from setuptools import setup, find_packages | |
2 | + | |
3 | +setup( | |
4 | + name='stremaz', | |
5 | + version='0.1', | |
6 | + packages=find_packages(), | |
7 | + install_requires=[ | |
8 | + 'fastapi', | |
9 | + 'uvicorn', | |
10 | + 'requests' | |
11 | + ], | |
12 | + | |
13 | + entry_points={ | |
14 | + 'console_scripts': [ | |
15 | + 'stremaz = app.main:app' | |
16 | + ] | |
17 | + } | |
18 | + | |
19 | +) | |
20 | +# Path: app/settings/config.py | |
21 | + | |
0 | 22 | |