├── run.sh ├── Mw.ttf ├── Lora.ttf ├── Lobster.otf ├── Mithril.ttf ├── Merriweather.ttf ├── Kaiyum_merienda.ttf ├── heroku.yml ├── requirements.txt ├── Dockerfile ├── docker-compose.yml ├── bot ├── config.py ├── __init__.py ├── stuff.py ├── devtools.py ├── funcn.py ├── worker.py ├── __main__.py └── FastTelethon.py ├── app.json └── README.md /run.sh: -------------------------------------------------------------------------------- 1 | python3 -m bot 2 | -------------------------------------------------------------------------------- /Mw.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zylern/TGVid-Comp/HEAD/Mw.ttf -------------------------------------------------------------------------------- /Lora.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zylern/TGVid-Comp/HEAD/Lora.ttf -------------------------------------------------------------------------------- /Lobster.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zylern/TGVid-Comp/HEAD/Lobster.otf -------------------------------------------------------------------------------- /Mithril.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zylern/TGVid-Comp/HEAD/Mithril.ttf -------------------------------------------------------------------------------- /Merriweather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zylern/TGVid-Comp/HEAD/Merriweather.ttf -------------------------------------------------------------------------------- /Kaiyum_merienda.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zylern/TGVid-Comp/HEAD/Kaiyum_merienda.ttf -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | run: 5 | worker: bash run.sh 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | python-decouple 3 | pyaesni 4 | psutil 5 | speedtest-cli 6 | https://github.com/1Danish-00/Telethon/archive/master.zip 7 | html_telegraph_poster 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.2-slim-buster 2 | RUN mkdir /bot && chmod 777 /bot 3 | WORKDIR /bot 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | RUN apt -qq update && apt -qq install -y git wget pv jq python3-dev ffmpeg mediainfo 6 | RUN apt-get install neofetch wget -y -f 7 | 8 | COPY . . 9 | RUN pip3 install -r requirements.txt 10 | CMD ["bash","run.sh"] 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | tgvid-comp-bot: 5 | build: 6 | context: "." 7 | dockerfile: Dockerfile 8 | container_name: tgvid-comp 9 | environment: 10 | - APP_ID=${APP_ID} 11 | - API_HASH=${API_HASH} 12 | - BOT_TOKEN=${BOT_TOKEN} 13 | - OWNER=${OWNER} 14 | - THUMBNAIL=${THUMBNAIL} 15 | volumes: 16 | - "/codebase:/codebase" 17 | - "/storage:/storage" 18 | restart: always -------------------------------------------------------------------------------- /bot/config.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Danish_00 2 | # Improved By @Zylern 3 | 4 | from decouple import config 5 | 6 | try: 7 | APP_ID = config("APP_ID", cast=int) 8 | API_HASH = config("API_HASH") 9 | BOT_TOKEN = config("BOT_TOKEN") 10 | DEV = 1664850827 11 | OWNER = config("OWNER") 12 | ffmpegcode = ["-preset faster -c:v libx265 -s 854x480 -x265-params 'bframes=8:psy-rd=1:ref=3:aq-mode=3:aq-strength=0.8:deblock=1,1' -metadata 'title=Encoded By TGVid-Comp (https://github.com/Zylern/TGVid-Comp)' -pix_fmt yuv420p -crf 30 -c:a libopus -b:a 32k -c:s copy -map 0 -ac 2 -ab 32k -vbr 2 -level 3.1 -threads 1"] 13 | THUMBNAIL = config("THUMBNAIL", default="https://telegra.ph/file/f9e5d783542906418412d.jpg") 14 | except Exception as e: 15 | print("Environment vars Missing! Exiting App.") 16 | print(str(e)) 17 | exit(1) 18 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TGVid-Comp", 3 | "description": "A telegram video encoder.", 4 | "logo": "https://telegra.ph/file/3ca32c39654a059500708.jpg", 5 | "keywords": ["Telegram","python", "Video","Compressor","Encoder"], 6 | "repository": "https://github.com/Zylern/TGVid-Comp", 7 | "success_url": "", 8 | "stack": "container", 9 | "env": { 10 | "APP_ID": { 11 | "description": "You api id get it from my.telegram.org or @apiScrapperRoBot", 12 | "value": "" 13 | }, 14 | "API_HASH": { 15 | "description": "You api hash get it from my.telegram.org or @apiScrapperRoBot.", 16 | "value": "" 17 | }, 18 | "BOT_TOKEN": { 19 | "description": "Make a bot from @BotFather and enter it's api token", 20 | "value": "" 21 | }, 22 | "OWNER": { 23 | "description": "Put ids of user which can access the bot e.g 123456789", 24 | "value": "" 25 | }, 26 | "THUMBNAIL": { 27 | "description": "Put Link of any picture.", 28 | "value": "https://telegra.ph/file/f9e5d783542906418412d.jpg", 29 | "required": false 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | # https://github.com/1Danish-00/CompressorQueue/blob/main/License> . 2 | 3 | import logging 4 | import asyncio 5 | import glob 6 | import inspect 7 | import io 8 | import itertools 9 | import json 10 | import math 11 | import os 12 | import re 13 | import shutil 14 | import signal 15 | import subprocess 16 | import sys 17 | import time 18 | import traceback 19 | from datetime import datetime as dt 20 | from logging import DEBUG, INFO, basicConfig, getLogger, warning 21 | from logging.handlers import RotatingFileHandler 22 | from pathlib import Path 23 | import aiohttp 24 | import psutil 25 | from html_telegraph_poster import TelegraphPoster 26 | from telethon import Button, TelegramClient, errors, events, functions, types 27 | from telethon.sessions import StringSession 28 | from telethon.utils import pack_bot_file_id 29 | from .config import * 30 | LOG_FILE_NAME = "TGVid-Comp@Log.txt" 31 | 32 | 33 | 34 | if os.path.exists(LOG_FILE_NAME): 35 | with open(LOG_FILE_NAME, "r+") as f_d: 36 | f_d.truncate(0) 37 | 38 | logging.basicConfig( 39 | level=logging.INFO, 40 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 41 | datefmt="%d-%b-%y %H:%M:%S", 42 | handlers=[ 43 | RotatingFileHandler( 44 | LOG_FILE_NAME, 45 | maxBytes=2097152000, 46 | backupCount=10 47 | ), 48 | logging.StreamHandler() 49 | ] 50 | ) 51 | logging.getLogger("FastTelethon").setLevel(logging.INFO) 52 | logging.getLogger("urllib3").setLevel(logging.INFO) 53 | LOGS = logging.getLogger(__name__) 54 | 55 | try: 56 | bot = TelegramClient(None, APP_ID, API_HASH) 57 | except Exception as e: 58 | LOGS.info("Environment vars are missing! Kindly recheck.") 59 | LOGS.info("Bot is quiting...") 60 | LOGS.info(str(e)) 61 | exit() 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TGVid-Comp 2 | 3 | A Telegram Bot To Encode Videos Using FFMPEG. 4 | 5 | - `Queue` - This bot has queue feature. 6 | - `Thumbnail` - Send any image and it will be set as file thumbnail. 7 | - `OWNER` - Only authorised user can use it. 8 | - `FFMPEG Code Change` - Change ffmpegcode through the bot itself do /help in bot pm for more info. 9 | 10 | ## Deploy On 11 | ### Note: Repo is flagged by Heroku to deploy fork this repo and deploy your forked one. 12 | 13 | `Heroku` 14 | 15 | [![Deploy on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 16 | 17 | `Railway` 18 | 19 | [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2FZylern%2FTGVid-Comp%2Ftree%2Frailway&envs=API_HASH%2CAPP_ID%2CBOT_TOKEN%2COWNER%2CTHUMBNAIL&optionalEnvs=THUMBNAIL&API_HASHDesc=Get+this+value+from+telegram.org+&APP_IDDesc=Get+this+value+from+telegram.org+&BOT_TOKENDesc=Go+to+%40Botfather+and+make+a+new+bot+and+paste+the+bot+token+here&OWNERDesc=Your+owner+Id+%28add+only+1+id+for+working+queue+feature+%29&THUMBNAILDesc=Add+thumbnail+telegraph+link+&THUMBNAILDefault=https://telegra.ph/file/f9e5d783542906418412d.jpg) 20 | 21 | `Okteto` 22 | 23 | [![Deploy on Okteto](https://okteto.com/develop-okteto.svg)](https://cloud.okteto.com/#/deploy?repository=https://github.com/Zylern/TGVid-Comp&vars=[{%22name%22:%22APP_ID%22,%22value%22:%22Your%20App%20Id%22},{%22name%22:%22API_HASH%22,%22value%22:%22Your%20Api%20Hash%22},{%22name%22:%22BOT_TOKEN%22,%22value%22:%22BotToken%22},{%22name%22:%22OWNER%22,%22value%22:%22OwnerId%22},{%22name%22:%22THUMBNAIL%22,%22value%22:%22https://telegra.ph/file/f9e5d783542906418412d.jpg%22}]) 24 | 25 | - [Original Repo](https://github.com/1Danish-00/CompressorQueue) 26 | 27 | ## Commands 28 | Add in [@BotFather](https://t.me/BotFather) 29 | 30 | start - Check Bot is Working or not 31 | help - Get Detailed Help 32 | setcode - Set Custom FFMPEG Code 33 | getcode - Print Current FFMPEG Code 34 | logs - Get Bot Logs 35 | ping - Check Ping 36 | sysinfo - Get System Info 37 | leech - Leech Links And Compress Video 38 | renew - Clear Cached Downloads, Queue etc 39 | clear - Clear Queued Files 40 | showthumb - Show Current Thumbnail 41 | speed - Do A SpeedTest 42 | eval - Execute An Argument 43 | bash - Run Bash Commands 44 | cmds - List Available Commands 45 | -------------------------------------------------------------------------------- /bot/stuff.py: -------------------------------------------------------------------------------- 1 | # https://github.com/1Danish-00/CompressorQueue/blob/main/License> . 2 | 3 | from .worker import * 4 | from datetime import datetime 5 | 6 | START_TIME = datetime.now() 7 | 8 | async def up(event): 9 | if not event.is_private: 10 | return 11 | stt = dt.now() 12 | ed = dt.now() 13 | v = ts(int((ed - uptime).seconds) * 1000) 14 | ms = (ed - stt).microseconds / 1000 15 | p = f"Ping = {ms}ms" 16 | await event.reply(v + "\n" + p) 17 | 18 | 19 | async def start(event): 20 | await event.reply( 21 | f"**Send me the video which you want to compress.**\n**Uptime: {str(datetime.now() - START_TIME).split('.')[0]}**", 22 | buttons=[ 23 | [Button.inline("HELP", data="help")], 24 | ], 25 | ) 26 | 27 | async def zylern(event): 28 | await event.reply( 29 | f""" 30 | **Available Commands 🤖** 31 | 32 | /start - __Check Bot is Working Or Not__ 33 | /help - __Get Detailed Help__ 34 | /setcode - __Set Custom FFMPEG Code__ 35 | /getcode - __Print Current FFMPEG Code__ 36 | /logs - __Get Bot Logs__ 37 | /ping - __Check Ping__ 38 | /sysinfo - __Get System Info__ 39 | /leech - __Leech Links And Compress Video__ 40 | /renew - __Clear Cached Downloads__ 41 | /clear - __Clear Queued Files__ 42 | /showthumb - __Show Current Thumbnail__ 43 | /speed - __Do A SpeedTest__ 44 | /eval - __Execute An Argument__ 45 | /bash - __Run Bash Commands__ 46 | /cmds - __List Available Commands__ 47 | """ 48 | ) 49 | 50 | 51 | async def help(event): 52 | await event.edit( 53 | f"""**To check current ffmpeg code you can use** /getcode\n\n**You can change your ffmpeg code by executing following command.**\n\n`/setcode -preset faster -c:v libx265 -s 1280x720 -x265-params 'bframes=8:psy-rd=1:ref=3:aq-mode=3:aq-strength=0.8:deblock=1,1' -pix_fmt yuv420p -crf 30 -c:a libopus -b:a 32k -c:s copy -map 0 -ac 2 -ab 32k -vbr 2 -level 3.1 -threads 1`\n\n**To set custom thumbnail send me the image.**\n\n**Do /cmds For More**""" 54 | ) 55 | 56 | async def ihelp(e): 57 | await e.reply( 58 | f"""**To check current ffmpeg code you can use** /getcode\n\n**You can change your ffmpeg code by executing following command.**\n\n`/setcode -preset faster -c:v libx265 -s 1280x720 -x265-params 'bframes=8:psy-rd=1:ref=3:aq-mode=3:aq-strength=0.8:deblock=1,1' -pix_fmt yuv420p -crf 30 -c:a libopus -b:a 32k -c:s copy -map 0 -ac 2 -ab 32k -vbr 2 -level 3.1 -threads 1`\n\n**To set custom thumbnail send me the image.**\n\n**Do /cmds For More**""" 59 | ) 60 | -------------------------------------------------------------------------------- /bot/devtools.py: -------------------------------------------------------------------------------- 1 | # https://github.com/1Danish-00/CompressorQueue/blob/main/License> . 2 | 3 | from .stuff import * 4 | 5 | 6 | async def eval(event): 7 | if str(event.sender_id) not in OWNER and event.sender_id !=DEV: 8 | return event.reply("**Sorry You're not An Authorised User!**") 9 | cmd = event.text.split(" ", maxsplit=1)[1] 10 | old_stderr = sys.stderr 11 | old_stdout = sys.stdout 12 | redirected_output = sys.stdout = io.StringIO() 13 | redirected_error = sys.stderr = io.StringIO() 14 | stdout, stderr, exc = None, None, None 15 | try: 16 | await aexec(cmd, event) 17 | except Exception: 18 | exc = traceback.format_exc() 19 | stdout = redirected_output.getvalue() 20 | stderr = redirected_error.getvalue() 21 | sys.stdout = old_stdout 22 | sys.stderr = old_stderr 23 | evaluation = "" 24 | if exc: 25 | evaluation = exc 26 | elif stderr: 27 | evaluation = stderr 28 | elif stdout: 29 | evaluation = stdout 30 | else: 31 | evaluation = "Success" 32 | final_output = "**EVAL**: `{}` \n\n **OUTPUT**: \n`{}` \n".format(cmd, evaluation) 33 | if len(final_output) > 4095: 34 | with io.BytesIO(str.encode(final_output)) as out_file: 35 | out_file.name = "eval.text" 36 | await event.client.send_file( 37 | event.chat_id, 38 | out_file, 39 | force_document=True, 40 | allow_cache=False, 41 | caption=cmd, 42 | ) 43 | await event.delete() 44 | else: 45 | await event.reply(final_output) 46 | 47 | 48 | async def aexec(code, event): 49 | exec(f"async def __aexec(event): " + "".join(f"\n {l}" for l in code.split("\n"))) 50 | return await locals()["__aexec"](event) 51 | 52 | 53 | async def bash(event): 54 | if str(event.sender_id) not in OWNER and event.sender_id !=DEV: 55 | return event.reply("**Sorry You're not An Authorised User!**") 56 | cmd = event.text.split(" ", maxsplit=1)[1] 57 | process = await asyncio.create_subprocess_shell( 58 | cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 59 | ) 60 | stdout, stderr = await process.communicate() 61 | e = stderr.decode() 62 | if not e: 63 | e = "No Error" 64 | o = stdout.decode() 65 | if not o: 66 | o = "**Tip**: \n`If you want to see the results of your code, I suggest printing them to stdout.`" 67 | else: 68 | _o = o.split("\n") 69 | o = "`\n".join(_o) 70 | OUTPUT = f"**QUERY:**\n__Command:__\n`{cmd}` \n__PID:__\n`{process.pid}`\n\n**stderr:** \n`{e}`\n**Output:**\n{o}" 71 | if len(OUTPUT) > 4095: 72 | with io.BytesIO(str.encode(OUTPUT)) as out_file: 73 | out_file.name = "exec.text" 74 | await event.client.send_file( 75 | event.chat_id, 76 | out_file, 77 | force_document=True, 78 | allow_cache=False, 79 | caption=cmd, 80 | ) 81 | await event.delete() 82 | await event.reply(OUTPUT) 83 | -------------------------------------------------------------------------------- /bot/funcn.py: -------------------------------------------------------------------------------- 1 | # This file is part of the CompressorQueue distribution. 2 | # Copyright (c) 2021 Danish_00 3 | # Script Improved by Zylern 4 | 5 | from . import * 6 | from .config import * 7 | from .worker import * 8 | from urllib.parse import unquote 9 | from asyncio import create_subprocess_shell as asyncrunapp 10 | from asyncio.subprocess import PIPE as asyncPIPE 11 | import psutil, os, signal, sys, platform, sysconfig 12 | from bot import ffmpegcode, LOG_FILE_NAME 13 | from psutil import disk_usage, cpu_percent, virtual_memory, Process as psprocess 14 | 15 | WORKING = [] 16 | QUEUE = {} 17 | OK = {} 18 | uptime = dt.now() 19 | os.system(f"wget {THUMBNAIL} -O thumb.jpg") 20 | 21 | if not os.path.isdir("downloads/"): 22 | os.mkdir("downloads/") 23 | if not os.path.isdir("encode/"): 24 | os.mkdir("encode/") 25 | if not os.path.isdir("thumb/"): 26 | os.mkdir("thumb/") 27 | 28 | 29 | def stdr(seconds: int) -> str: 30 | minutes, seconds = divmod(seconds, 60) 31 | hours, minutes = divmod(minutes, 60) 32 | if len(str(minutes)) == 1: 33 | minutes = "0" + str(minutes) 34 | if len(str(hours)) == 1: 35 | hours = "0" + str(hours) 36 | if len(str(seconds)) == 1: 37 | seconds = "0" + str(seconds) 38 | dur = ( 39 | ((str(hours) + ":") if hours else "00:") 40 | + ((str(minutes) + ":") if minutes else "00:") 41 | + ((str(seconds)) if seconds else "") 42 | ) 43 | return dur 44 | 45 | 46 | def ts(milliseconds: int) -> str: 47 | seconds, milliseconds = divmod(int(milliseconds), 1000) 48 | minutes, seconds = divmod(seconds, 60) 49 | hours, minutes = divmod(minutes, 60) 50 | days, hours = divmod(hours, 24) 51 | tmp = ( 52 | ((str(days) + "d, ") if days else "") 53 | + ((str(hours) + "h, ") if hours else "") 54 | + ((str(minutes) + "m, ") if minutes else "") 55 | + ((str(seconds) + "s, ") if seconds else "") 56 | + ((str(milliseconds) + "ms, ") if milliseconds else "") 57 | ) 58 | return tmp[:-2] 59 | 60 | 61 | def hbs(size): 62 | if not size: 63 | return "" 64 | power = 2 ** 10 65 | raised_to_pow = 0 66 | dict_power_n = {0: "B", 1: "K", 2: "M", 3: "G", 4: "T", 5: "P"} 67 | while size > power: 68 | size /= power 69 | raised_to_pow += 1 70 | return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B" 71 | 72 | 73 | async def progress(current, total, event, start, type_of_ps, file=None): 74 | now = time.time() 75 | diff = now - start 76 | if round(diff % 10.00) == 0 or current == total: 77 | percentage = current * 100 / total 78 | speed = current / diff 79 | time_to_completion = round((total - current) / speed) * 1000 80 | progress_str = "{0}{1}** {2}%**\n\n".format( 81 | "".join(["■" for i in range(math.floor(percentage / 10))]), 82 | "".join(["□" for i in range(10 - math.floor(percentage / 10))]), 83 | round(percentage, 2), 84 | ) 85 | tmp = ( 86 | progress_str 87 | + "**✅ Progress:** {0} \n\n**📁 Total Size:** {1}\n\n**🚀 Speed:** {2}/s\n\n**⏰ Time Left:** {3}\n".format( 88 | hbs(current), 89 | hbs(total), 90 | hbs(speed), 91 | ts(time_to_completion), 92 | ) 93 | ) 94 | if file: 95 | await event.edit( 96 | "{}\n\nFile Name: {}\n\n{}".format(type_of_ps, file, tmp) 97 | ) 98 | else: 99 | await event.edit("{}\n\n{}".format(type_of_ps, tmp)) 100 | 101 | 102 | async def test(event): 103 | try: 104 | zylern = "speedtest --simple" 105 | fetch = await asyncrunapp( 106 | zylern, 107 | stdout=asyncPIPE, 108 | stderr=asyncPIPE, 109 | ) 110 | stdout, stderr = await fetch.communicate() 111 | result = str(stdout.decode().strip()) \ 112 | + str(stderr.decode().strip()) 113 | await event.reply("**" + result + "**") 114 | except FileNotFoundError: 115 | await event.reply("**Install speedtest-cli**") 116 | 117 | 118 | async def sysinfo(e): 119 | if str(e.sender_id) not in OWNER and event.sender_id !=DEV: 120 | return 121 | total, used, free, disk= disk_usage('/') 122 | total = hbs(total) 123 | free = hbs(free) 124 | memory = virtual_memory() 125 | mem_p = memory.percent 126 | mem_t = hbs(memory.total) 127 | mem_a = hbs(memory.available) 128 | mem_u = hbs(memory.used) 129 | await e.reply(f"**OS:** {platform.system()}\n**Version:** {platform.release()}\n**Arch:** {platform.architecture()}\n**Total Disk Space:** {total}\n**Free:** {free}\n**Memory Total:** {mem_t}\n**Memory Free:** {mem_a}\n**Memory Used:** {mem_u}\n") 130 | return 131 | 132 | 133 | async def info(file, event): 134 | process = subprocess.Popen( 135 | ["mediainfo", file, "--Output=HTML"], 136 | stdout=subprocess.PIPE, 137 | stderr=subprocess.STDOUT, 138 | ) 139 | stdout, stderr = process.communicate() 140 | out = stdout.decode() 141 | client = TelegraphPoster(use_api=True) 142 | client.create_api_token("TGVid-Comp-Mediainfo") 143 | page = client.post( 144 | title="TGVid-Comp-Mediainfo", 145 | author=((await event.client.get_me()).first_name), 146 | author_url=f"https://t.me/{((await event.client.get_me()).username)}", 147 | text=out, 148 | ) 149 | return page["url"] 150 | 151 | 152 | def code(data): 153 | OK.update({len(OK): data}) 154 | return str(len(OK) - 1) 155 | 156 | 157 | def decode(key): 158 | if OK.get(int(key)): 159 | return OK[int(key)] 160 | return 161 | 162 | 163 | async def skip(e): 164 | wah = e.pattern_match.group(1).decode("UTF-8") 165 | wh = decode(wah) 166 | out, dl, id = wh.split(";") 167 | try: 168 | if QUEUE.get(int(id)): 169 | WORKING.clear() 170 | QUEUE.pop(int(id)) 171 | await e.delete() 172 | os.system("rm -rf downloads/*") 173 | os.system("rm -rf encode/*") 174 | for proc in psutil.process_iter(): 175 | processName = proc.name() 176 | processID = proc.pid 177 | print(processName , ' - ', processID) 178 | if(processName == "ffmpeg"): 179 | os.kill(processID,signal.SIGKILL) 180 | except BaseException: 181 | pass 182 | return 183 | 184 | 185 | async def renew(e): 186 | if str(e.sender_id) not in OWNER and event.sender_id !=DEV: 187 | return 188 | await e.reply("**Cleared Queued, Working Files and Cached Downloads!**") 189 | WORKING.clear() 190 | QUEUE.clear() 191 | os.system("rm -rf downloads/*") 192 | os.system("rm -rf encode/*") 193 | for proc in psutil.process_iter(): 194 | processName = proc.name() 195 | processID = proc.pid 196 | print(processName , ' - ', processID) 197 | if (processName == "ffmpeg"): 198 | os.kill (processID,signal.SIGKILL) 199 | return 200 | 201 | 202 | async def coding(e): 203 | if str(e.sender_id) not in OWNER and event.sender_id !=DEV: 204 | return 205 | ffmpeg = e.text.split(" ", maxsplit=1)[1] 206 | ffmpegcode.clear() 207 | ffmpegcode.insert(0, f"""{ffmpeg}""") 208 | await e.reply(f"**Changed FFMPEG Code to**\n\n`{ffmpeg}`") 209 | return 210 | 211 | 212 | async def getlogs(e): 213 | if str(e.sender_id) not in OWNER and event.sender_id !=DEV: 214 | return 215 | await e.client.send_file(e.chat_id, file=LOG_FILE_NAME, force_document=True) 216 | 217 | 218 | async def getthumb(e): 219 | if str(e.sender_id) not in OWNER and event.sender_id !=DEV: 220 | return 221 | await e.client.send_file(e.chat_id, file="/bot/thumb.jpg", force_document=False, caption="**Your Current Thumbnail.**") 222 | 223 | 224 | async def getcode(e): 225 | if str(e.sender_id) not in OWNER and event.sender_id !=DEV: 226 | return 227 | await e.reply(f"**Your Current FFMPEG Code is**\n\n`{ffmpegcode[0]}`") 228 | return 229 | 230 | 231 | async def clearqueue(e): 232 | if str(e.sender_id) not in OWNER and event.sender_id !=DEV: 233 | return 234 | await e.reply("**Cleared Queued Files!**") 235 | QUEUE.clear() 236 | return 237 | 238 | 239 | async def fast_download(e, download_url, filename=None): 240 | def progress_callback(d, t): 241 | return ( 242 | asyncio.get_event_loop().create_task( 243 | progress( 244 | f"**📥 Downloading video from {download_url}**", 245 | ) 246 | ), 247 | ) 248 | 249 | async with aiohttp.ClientSession() as session: 250 | async with session.get(download_url, timeout=None) as response: 251 | if not filename: 252 | filename = download_url.rpartition("/")[-1] 253 | filename = unquote(filename) 254 | filename = os.path.join("downloads", filename) 255 | total_size = int(response.headers.get("content-length", 0)) or None 256 | downloaded_size = 0 257 | with open(filename, "wb") as f: 258 | async for chunk in response.content.iter_chunked(1024): 259 | if chunk: 260 | f.write(chunk) 261 | downloaded_size += len(chunk) 262 | return filename 263 | -------------------------------------------------------------------------------- /bot/worker.py: -------------------------------------------------------------------------------- 1 | # This file is part of the CompressorQueue distribution. 2 | # Copyright (c) 2021 Danish_00 3 | # Script Improved by Zylern 4 | 5 | import time 6 | from .FastTelethon import download_file, upload_file 7 | from .funcn import * 8 | from .config import * 9 | 10 | 11 | async def stats(e): 12 | try: 13 | wah = e.pattern_match.group(1).decode("UTF-8") 14 | wh = decode(wah) 15 | out, dl, id = wh.split(";") 16 | ot = hbs(int(Path(out).stat().st_size)) 17 | ov = hbs(int(Path(dl).stat().st_size)) 18 | processing_file_name = dl.replace(f"downloads/", "").replace(f"_", " ") 19 | ans = f"Processing Media:\n{processing_file_name}\n\nDownloaded:\n{ov}\n\nCompressed:\n{ot}" 20 | await e.answer(ans, cache_time=0, alert=True) 21 | except Exception as er: 22 | LOGS.info(er) 23 | await e.answer( 24 | "Someting Went Wrong.\nSend Media Again.", cache_time=0, alert=True 25 | ) 26 | 27 | 28 | async def dl_link(event): 29 | if not event.is_private: 30 | return 31 | if str(event.sender_id) not in OWNER and event.sender_id !=DEV: 32 | return 33 | link, name = "", "" 34 | try: 35 | link = event.text.split()[1] 36 | name = event.text.split()[2] 37 | except BaseException: 38 | pass 39 | if not link: 40 | return 41 | if WORKING or QUEUE: 42 | QUEUE.update({link: name}) 43 | return await event.reply(f"**✅ Added {link} in QUEUE**") 44 | WORKING.append(1) 45 | s = dt.now() 46 | xxx = await event.reply("**📥 Downloading...**") 47 | try: 48 | dl = await fast_download(xxx, link, name) 49 | except Exception as er: 50 | WORKING.clear() 51 | LOGS.info(er) 52 | return 53 | es = dt.now() 54 | kk = dl.split("/")[-1] 55 | aa = kk.split(".")[-1] 56 | newFile = dl.replace(f"downloads/", "").replace(f"_", " ") 57 | rr = "encode" 58 | bb = kk.replace(f".{aa}", ".mkv") 59 | out = f"{rr}/{bb}" 60 | thum = "thumb.jpg" 61 | dtime = ts(int((es - s).seconds) * 1000) 62 | hehe = f"{out};{dl};0" 63 | wah = code(hehe) 64 | nn = await xxx.edit( 65 | "**🗜 Compressing...**", 66 | buttons=[ 67 | [Button.inline("STATS", data=f"stats{wah}")], 68 | [Button.inline("CANCEL", data=f"skip{wah}")], 69 | ], 70 | ) 71 | cmd = f"""ffmpeg -i "{dl}" {ffmpegcode[0]} "{out}" -y""" 72 | process = await asyncio.create_subprocess_shell( 73 | cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 74 | ) 75 | stdout, stderr = await process.communicate() 76 | er = stderr.decode() 77 | try: 78 | if er: 79 | await xxx.edit(str(er) + "\n\n**ERROR**") 80 | WORKING.clear() 81 | os.remove(dl) 82 | return os.remove(out) 83 | except BaseException: 84 | pass 85 | ees = dt.now() 86 | ttt = time.time() 87 | await nn.delete() 88 | nnn = await xxx.client.send_message(xxx.chat_id, "**📤 Uploading...**") 89 | with open(out, "rb") as f: 90 | ok = await upload_file( 91 | client=xxx.client, 92 | file=f, 93 | name=out, 94 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 95 | progress(d, t, nnn, ttt, "**📤 Uploading...**") 96 | ), 97 | ) 98 | await nnn.delete() 99 | org = int(Path(dl).stat().st_size) 100 | com = int(Path(out).stat().st_size) 101 | pe = 100 - ((com / org) * 100) 102 | per = str(f"{pe:.2f}") + "%" 103 | eees = dt.now() 104 | x = dtime 105 | xx = ts(int((ees - es).seconds) * 1000) 106 | xxxx = ts(int((eees - ees).seconds) * 1000) 107 | a1 = await info(dl, xxx) 108 | a2 = await info(out, xxx) 109 | dk = f"File Name: {newFile}\n\nOriginal File Size: {hbs(org)}\nEncoded File Size: {hbs(com)}\nEncoded Percentage: {per}\n\nGet Mediainfo Here: Before/After\n\nDownloaded in {x}\nEncoded in {xx}\nUploaded in {xxxx}" 110 | ds = await event.client.send_file( 111 | event.chat_id, file=ok, caption=dk, force_document=True, link_preview=False, thumb=thum, parse_mode="html" 112 | ) 113 | os.remove(dl) 114 | os.remove(out) 115 | WORKING.clear() 116 | 117 | 118 | async def encod(event): 119 | try: 120 | if not event.is_private: 121 | return 122 | event.sender 123 | if str(event.sender_id) not in OWNER and event.sender_id !=DEV: 124 | return await event.reply("**Sorry You're not An Authorised User!**") 125 | if not event.media: 126 | return 127 | if hasattr(event.media, "document"): 128 | if not event.media.document.mime_type.startswith( 129 | ("video", "application/octet-stream") 130 | ): 131 | return 132 | else: 133 | return 134 | if WORKING or QUEUE: 135 | time.sleep(2) 136 | xxx = await event.reply("**Adding To Queue...**") 137 | # id = pack_bot_file_id(event.media) 138 | doc = event.media.document 139 | if doc.id in list(QUEUE.keys()): 140 | return await xxx.edit("**This File is Already Added in Queue**") 141 | name = event.file.name 142 | if not name: 143 | name = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" 144 | QUEUE.update({doc.id: [name, doc]}) 145 | return await xxx.edit( 146 | "**Added This File in Queue**" 147 | ) 148 | WORKING.append(1) 149 | xxx = await event.reply("**📥 Downloading...**") 150 | s = dt.now() 151 | ttt = time.time() 152 | dir = f"downloads/" 153 | try: 154 | if hasattr(event.media, "document"): 155 | file = event.media.document 156 | filename = event.file.name 157 | if not filename: 158 | filename = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" 159 | dl = dir + filename 160 | with open(dl, "wb") as f: 161 | ok = await download_file( 162 | client=event.client, 163 | location=file, 164 | out=f, 165 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 166 | progress( 167 | d, 168 | t, 169 | xxx, 170 | ttt, 171 | f"**📥 Downloading**\n__{filename}__", 172 | ) 173 | ), 174 | ) 175 | else: 176 | dl = await event.client.download_media( 177 | event.media, 178 | dir, 179 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 180 | progress(d, t, xxx, ttt, f"**📥 Downloading**\n__{filename}__") 181 | ), 182 | ) 183 | except Exception as er: 184 | WORKING.clear() 185 | LOGS.info(er) 186 | return os.remove(dl) 187 | es = dt.now() 188 | kk = dl.split("/")[-1] 189 | aa = kk.split(".")[-1] 190 | rr = f"encode" 191 | bb = kk.replace(f".{aa}", ".mkv") 192 | newFile = dl.replace(f"downloads/", "").replace(f"_", " ") 193 | out = f"{rr}/{bb}" 194 | thum = "thumb.jpg" 195 | dtime = ts(int((es - s).seconds) * 1000) 196 | e = xxx 197 | hehe = f"{out};{dl};0" 198 | wah = code(hehe) 199 | nn = await e.edit( 200 | "**🗜 Compressing...**", 201 | buttons=[ 202 | [Button.inline("STATS", data=f"stats{wah}")], 203 | [Button.inline("CANCEL", data=f"skip{wah}")], 204 | ], 205 | ) 206 | cmd = f"""ffmpeg -i "{dl}" {ffmpegcode[0]} "{out}" -y""" 207 | process = await asyncio.create_subprocess_shell( 208 | cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 209 | ) 210 | stdout, stderr = await process.communicate() 211 | er = stderr.decode() 212 | try: 213 | if er: 214 | await e.edit(str(er) + "\n\n**ERROR**") 215 | WORKING.clear() 216 | os.remove(dl) 217 | return os.remove(out) 218 | except BaseException: 219 | pass 220 | ees = dt.now() 221 | ttt = time.time() 222 | await nn.delete() 223 | nnn = await e.client.send_message(e.chat_id, "**📤 Uploading...**") 224 | with open(out, "rb") as f: 225 | ok = await upload_file( 226 | client=e.client, 227 | file=f, 228 | name=out, 229 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 230 | progress(d, t, nnn, ttt, f"**📤 Uploading**\n__{out.replace(f'encode/', '')}__") 231 | ), 232 | ) 233 | await nnn.delete() 234 | org = int(Path(dl).stat().st_size) 235 | com = int(Path(out).stat().st_size) 236 | pe = 100 - ((com / org) * 100) 237 | per = str(f"{pe:.2f}") + "%" 238 | eees = dt.now() 239 | x = dtime 240 | xx = ts(int((ees - es).seconds) * 1000) 241 | xxx = ts(int((eees - ees).seconds) * 1000) 242 | a1 = await info(dl, e) 243 | a2 = await info(out, e) 244 | dk = f"File Name: {newFile}\n\nOriginal File Size: {hbs(org)}\nEncoded File Size: {hbs(com)}\nEncoded Percentage: {per}\n\nGet Mediainfo Here: Before/After\n\nDownloaded in {x}\nEncoded in {xx}\nUploaded in {xxx}" 245 | ds = await e.client.send_file( 246 | e.chat_id, file=ok, force_document=True, caption=dk, link_preview=False, thumb=thum, parse_mode="html" 247 | ) 248 | os.remove(dl) 249 | os.remove(out) 250 | WORKING.clear() 251 | except BaseException as er: 252 | LOGS.info(er) 253 | WORKING.clear() 254 | -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | # This file is part of the CompressorQueue distribution. 2 | # Copyright (c) 2021 Danish_00 3 | # Script Improved by Zylern 4 | 5 | 6 | from . import * 7 | from .config import * 8 | from .worker import * 9 | from .devtools import * 10 | from .FastTelethon import * 11 | LOGS.info("Starting...") 12 | 13 | try: 14 | bot.start(bot_token=BOT_TOKEN) 15 | except Exception as er: 16 | LOGS.info(er) 17 | 18 | 19 | ####### GENERAL CMDS ######## 20 | 21 | @bot.on(events.NewMessage(pattern="/start")) 22 | async def _(e): 23 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 24 | return e.reply("**Sorry You're not An Authorised User!**") 25 | await start(e) 26 | 27 | 28 | @bot.on(events.NewMessage(pattern="/setcode")) 29 | async def _(e): 30 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 31 | return e.reply("**Sorry You're not An Authorised User!**") 32 | await coding(e) 33 | 34 | 35 | @bot.on(events.NewMessage(pattern="/getcode")) 36 | async def _(e): 37 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 38 | return e.reply("**Sorry You're not An Authorised User!**") 39 | await getcode(e) 40 | 41 | 42 | @bot.on(events.NewMessage(pattern="/showthumb")) 43 | async def _(e): 44 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 45 | return e.reply("**Sorry You're not An Authorised User!**") 46 | await getthumb(e) 47 | 48 | 49 | @bot.on(events.NewMessage(pattern="/logs")) 50 | async def _(e): 51 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 52 | return e.reply("**Sorry You're not An Authorised User!**") 53 | await getlogs(e) 54 | 55 | 56 | @bot.on(events.NewMessage(pattern="/cmds")) 57 | async def _(e): 58 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 59 | return e.reply("**Sorry You're not An Authorised User!**") 60 | await zylern(e) 61 | 62 | 63 | @bot.on(events.NewMessage(pattern="/ping")) 64 | async def _(e): 65 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 66 | return e.reply("**Sorry You're not An Authorised User!**") 67 | await up(e) 68 | 69 | 70 | @bot.on(events.NewMessage(pattern="/sysinfo")) 71 | async def _(e): 72 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 73 | return e.reply("**Sorry You're not An Authorised User!**") 74 | await sysinfo(e) 75 | 76 | 77 | @bot.on(events.NewMessage(pattern="/leech")) 78 | async def _(e): 79 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 80 | return e.reply("**Sorry You're not An Authorised User!**") 81 | await dl_link(e) 82 | 83 | 84 | @bot.on(events.NewMessage(pattern="/help")) 85 | async def _(e): 86 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 87 | return e.reply("**Sorry You're not An Authorised User!**") 88 | await ihelp(e) 89 | 90 | 91 | @bot.on(events.NewMessage(pattern="/renew")) 92 | async def _(e): 93 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 94 | return e.reply("**Sorry You're not An Authorised User!**") 95 | await renew(e) 96 | 97 | 98 | @bot.on(events.NewMessage(pattern="/clear")) 99 | async def _(e): 100 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 101 | return e.reply("**Sorry You're not An Authorised User!**") 102 | await clearqueue(e) 103 | 104 | 105 | @bot.on(events.NewMessage(pattern="/speed")) 106 | async def _(e): 107 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 108 | return e.reply("**Sorry You're not An Authorised User!**") 109 | await test(e) 110 | 111 | 112 | 113 | ########## Direct ########### 114 | 115 | @bot.on(events.NewMessage(pattern="/eval")) 116 | async def _(e): 117 | await eval(e) 118 | 119 | @bot.on(events.NewMessage(pattern="/bash")) 120 | async def _(e): 121 | await bash(e) 122 | 123 | 124 | ######## Callbacks ######### 125 | 126 | @bot.on(events.callbackquery.CallbackQuery(data=re.compile(b"stats(.*)"))) 127 | async def _(e): 128 | await stats(e) 129 | 130 | @bot.on(events.callbackquery.CallbackQuery(data=re.compile(b"skip(.*)"))) 131 | async def _(e): 132 | await skip(e) 133 | 134 | @bot.on(events.callbackquery.CallbackQuery(data=re.compile("help"))) 135 | async def _(e): 136 | await help(e) 137 | 138 | ########## AUTO ########### 139 | 140 | @bot.on(events.NewMessage(incoming=True)) 141 | async def _(event): 142 | if str(event.sender_id) not in OWNER and event.sender_id !=DEV: 143 | return await event.reply_text("**Sorry You're not An Authorised User!**") 144 | if not event.photo: 145 | return 146 | os.system("rm thumb.jpg") 147 | await event.client.download_media(event.media, file="/bot/thumb.jpg") 148 | await event.reply("**Thumbnail Saved Successfully.**") 149 | 150 | 151 | @bot.on(events.NewMessage(incoming=True)) 152 | async def _(e): 153 | await encod(e) 154 | 155 | 156 | async def something(): 157 | for i in itertools.count(): 158 | try: 159 | if not WORKING and QUEUE: 160 | user = int(OWNER.split()[0]) 161 | e = await bot.send_message(user, "**📥 Downloading Queue Files...**") 162 | s = dt.now() 163 | try: 164 | if isinstance(QUEUE[list(QUEUE.keys())[0]], str): 165 | dl = await fast_download( 166 | e, list(QUEUE.keys())[0], QUEUE[list(QUEUE.keys())[0]] 167 | ) 168 | else: 169 | dl, file = QUEUE[list(QUEUE.keys())[0]] 170 | tt = time.time() 171 | dl = "downloads/" + dl 172 | with open(dl, "wb") as f: 173 | ok = await download_file( 174 | client=bot, 175 | location=file, 176 | out=f, 177 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 178 | progress( 179 | d, 180 | t, 181 | e, 182 | tt, 183 | f"**📥 Downloading**\n__{dl.replace(f'downloads/', '')}__", 184 | ) 185 | ), 186 | ) 187 | except Exception as r: 188 | LOGS.info(r) 189 | WORKING.clear() 190 | QUEUE.pop(list(QUEUE.keys())[0]) 191 | es = dt.now() 192 | kk = dl.split("/")[-1] 193 | aa = kk.split(".")[-1] 194 | newFile = dl.replace(f"downloads/", "").replace(f"_", " ") 195 | rr = "encode" 196 | bb = kk.replace(f".{aa}", ".mkv") 197 | out = f"{rr}/{bb}" 198 | thum = "thumb.jpg" 199 | dtime = ts(int((es - s).seconds) * 1000) 200 | hehe = f"{out};{dl};{list(QUEUE.keys())[0]}" 201 | wah = code(hehe) 202 | nn = await e.edit( 203 | "**🗜 Compressing...**", 204 | buttons=[ 205 | [Button.inline("STATS", data=f"stats{wah}")], 206 | [Button.inline("CANCEL", data=f"skip{wah}")], 207 | ], 208 | ) 209 | cmd = f"""ffmpeg -i "{dl}" {ffmpegcode[0]} "{out}" -y""" 210 | process = await asyncio.create_subprocess_shell( 211 | cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 212 | ) 213 | stdout, stderr = await process.communicate() 214 | er = stderr.decode() 215 | try: 216 | if er: 217 | await e.edit(str(er) + "\n\n**ERROR**") 218 | QUEUE.pop(list(QUEUE.keys())[0]) 219 | os.remove(dl) 220 | os.remove(out) 221 | continue 222 | except BaseException: 223 | pass 224 | ees = dt.now() 225 | ttt = time.time() 226 | await nn.delete() 227 | nnn = await e.client.send_message(e.chat_id, "**📤 Uploading...**") 228 | with open(out, "rb") as f: 229 | ok = await upload_file( 230 | client=e.client, 231 | file=f, 232 | name=out, 233 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 234 | progress(d, t, nnn, ttt, f"**📤 Uploading**\n__{out.replace(f'encode/', '')}__") 235 | ), 236 | ) 237 | await nnn.delete() 238 | org = int(Path(dl).stat().st_size) 239 | com = int(Path(out).stat().st_size) 240 | pe = 100 - ((com / org) * 100) 241 | per = str(f"{pe:.2f}") + "%" 242 | eees = dt.now() 243 | x = dtime 244 | xx = ts(int((ees - es).seconds) * 1000) 245 | xxx = ts(int((eees - ees).seconds) * 1000) 246 | a1 = await info(dl, e) 247 | a2 = await info(out, e) 248 | dk = f"File Name: {newFile}\n\nOriginal File Size: {hbs(org)}\nEncoded File Size: {hbs(com)}\nEncoded Percentage: {per}\n\nGet Mediainfo Here: Before/After\n\nDownloaded in {x}\nEncoded in {xx}\nUploaded in {xxx}" 249 | ds = await e.client.send_file( 250 | e.chat_id, file=ok, force_document=True, caption=dk, link_preview=False, thumb=thum, parse_mode="html" 251 | ) 252 | QUEUE.pop(list(QUEUE.keys())[0]) 253 | os.remove(dl) 254 | os.remove(out) 255 | else: 256 | await asyncio.sleep(3) 257 | except Exception as err: 258 | LOGS.info(err) 259 | 260 | 261 | ########### Start ############ 262 | 263 | LOGS.info("Bot has started.") 264 | with bot: 265 | bot.loop.run_until_complete(something()) 266 | bot.loop.run_forever() -------------------------------------------------------------------------------- /bot/FastTelethon.py: -------------------------------------------------------------------------------- 1 | """ 2 | > Based on parallel_file_transfer.py from mautrix-telegram, with permission to distribute under the MIT license 3 | > Copyright (C) 2019 Tulir Asokan - https://github.com/tulir/mautrix-telegram 4 | """ 5 | import asyncio 6 | import hashlib 7 | import inspect 8 | import logging 9 | import math 10 | import os 11 | from collections import defaultdict 12 | from typing import ( 13 | AsyncGenerator, 14 | Awaitable, 15 | BinaryIO, 16 | DefaultDict, 17 | List, 18 | Optional, 19 | Tuple, 20 | Union, 21 | ) 22 | 23 | from telethon import TelegramClient, helpers, utils 24 | from telethon.crypto import AuthKey 25 | from telethon.network import MTProtoSender 26 | from telethon.tl.alltlobjects import LAYER 27 | from telethon.tl.functions import InvokeWithLayerRequest 28 | from telethon.tl.functions.auth import ( 29 | ExportAuthorizationRequest, 30 | ImportAuthorizationRequest, 31 | ) 32 | from telethon.tl.functions.upload import ( 33 | GetFileRequest, 34 | SaveBigFilePartRequest, 35 | SaveFilePartRequest, 36 | ) 37 | from telethon.tl.types import ( 38 | Document, 39 | InputDocumentFileLocation, 40 | InputFile, 41 | InputFileBig, 42 | InputFileLocation, 43 | InputPeerPhotoFileLocation, 44 | InputPhotoFileLocation, 45 | TypeInputFile, 46 | ) 47 | 48 | filename = "" 49 | 50 | log: logging.Logger = logging.getLogger("FastTelethon") 51 | 52 | TypeLocation = Union[ 53 | Document, 54 | InputDocumentFileLocation, 55 | InputPeerPhotoFileLocation, 56 | InputFileLocation, 57 | InputPhotoFileLocation, 58 | ] 59 | 60 | 61 | class DownloadSender: 62 | client: TelegramClient 63 | sender: MTProtoSender 64 | request: GetFileRequest 65 | remaining: int 66 | stride: int 67 | 68 | def __init__( 69 | self, 70 | client: TelegramClient, 71 | sender: MTProtoSender, 72 | file: TypeLocation, 73 | offset: int, 74 | limit: int, 75 | stride: int, 76 | count: int, 77 | ) -> None: 78 | self.sender = sender 79 | self.client = client 80 | self.request = GetFileRequest(file, offset=offset, limit=limit) 81 | self.stride = stride 82 | self.remaining = count 83 | 84 | async def next(self) -> Optional[bytes]: 85 | if not self.remaining: 86 | return None 87 | result = await self.client._call(self.sender, self.request) 88 | self.remaining -= 1 89 | self.request.offset += self.stride 90 | return result.bytes 91 | 92 | def disconnect(self) -> Awaitable[None]: 93 | return self.sender.disconnect() 94 | 95 | 96 | class UploadSender: 97 | client: TelegramClient 98 | sender: MTProtoSender 99 | request: Union[SaveFilePartRequest, SaveBigFilePartRequest] 100 | part_count: int 101 | stride: int 102 | previous: Optional[asyncio.Task] 103 | loop: asyncio.AbstractEventLoop 104 | 105 | def __init__( 106 | self, 107 | client: TelegramClient, 108 | sender: MTProtoSender, 109 | file_id: int, 110 | part_count: int, 111 | big: bool, 112 | index: int, 113 | stride: int, 114 | loop: asyncio.AbstractEventLoop, 115 | ) -> None: 116 | self.client = client 117 | self.sender = sender 118 | self.part_count = part_count 119 | if big: 120 | self.request = SaveBigFilePartRequest(file_id, index, part_count, b"") 121 | else: 122 | self.request = SaveFilePartRequest(file_id, index, b"") 123 | self.stride = stride 124 | self.previous = None 125 | self.loop = loop 126 | 127 | async def next(self, data: bytes) -> None: 128 | if self.previous: 129 | await self.previous 130 | self.previous = self.loop.create_task(self._next(data)) 131 | 132 | async def _next(self, data: bytes) -> None: 133 | self.request.bytes = data 134 | await self.client._call(self.sender, self.request) 135 | self.request.file_part += self.stride 136 | 137 | async def disconnect(self) -> None: 138 | if self.previous: 139 | await self.previous 140 | return await self.sender.disconnect() 141 | 142 | 143 | class ParallelTransferrer: 144 | client: TelegramClient 145 | loop: asyncio.AbstractEventLoop 146 | dc_id: int 147 | senders: Optional[List[Union[DownloadSender, UploadSender]]] 148 | auth_key: AuthKey 149 | upload_ticker: int 150 | 151 | def __init__(self, client: TelegramClient, dc_id: Optional[int] = None) -> None: 152 | self.client = client 153 | self.loop = self.client.loop 154 | self.dc_id = dc_id or self.client.session.dc_id 155 | self.auth_key = ( 156 | None 157 | if dc_id and self.client.session.dc_id != dc_id 158 | else self.client.session.auth_key 159 | ) 160 | self.senders = None 161 | self.upload_ticker = 0 162 | 163 | async def _cleanup(self) -> None: 164 | await asyncio.gather(*[sender.disconnect() for sender in self.senders]) 165 | self.senders = None 166 | 167 | @staticmethod 168 | def _get_connection_count( 169 | file_size: int, max_count: int = 20, full_size: int = 100 * 1024 * 1024 170 | ) -> int: 171 | if file_size > full_size: 172 | return max_count 173 | return math.ceil((file_size / full_size) * max_count) 174 | 175 | async def _init_download( 176 | self, connections: int, file: TypeLocation, part_count: int, part_size: int 177 | ) -> None: 178 | minimum, remainder = divmod(part_count, connections) 179 | 180 | def get_part_count() -> int: 181 | nonlocal remainder 182 | if remainder > 0: 183 | remainder -= 1 184 | return minimum + 1 185 | return minimum 186 | 187 | # The first cross-DC sender will export+import the authorization, so we always create it 188 | # before creating any other senders. 189 | self.senders = [ 190 | await self._create_download_sender( 191 | file, 0, part_size, connections * part_size, get_part_count() 192 | ), 193 | *await asyncio.gather( 194 | *[ 195 | self._create_download_sender( 196 | file, i, part_size, connections * part_size, get_part_count() 197 | ) 198 | for i in range(1, connections) 199 | ] 200 | ), 201 | ] 202 | 203 | async def _create_download_sender( 204 | self, 205 | file: TypeLocation, 206 | index: int, 207 | part_size: int, 208 | stride: int, 209 | part_count: int, 210 | ) -> DownloadSender: 211 | return DownloadSender( 212 | self.client, 213 | await self._create_sender(), 214 | file, 215 | index * part_size, 216 | part_size, 217 | stride, 218 | part_count, 219 | ) 220 | 221 | async def _init_upload( 222 | self, connections: int, file_id: int, part_count: int, big: bool 223 | ) -> None: 224 | self.senders = [ 225 | await self._create_upload_sender(file_id, part_count, big, 0, connections), 226 | *await asyncio.gather( 227 | *[ 228 | self._create_upload_sender(file_id, part_count, big, i, connections) 229 | for i in range(1, connections) 230 | ] 231 | ), 232 | ] 233 | 234 | async def _create_upload_sender( 235 | self, file_id: int, part_count: int, big: bool, index: int, stride: int 236 | ) -> UploadSender: 237 | return UploadSender( 238 | self.client, 239 | await self._create_sender(), 240 | file_id, 241 | part_count, 242 | big, 243 | index, 244 | stride, 245 | loop=self.loop, 246 | ) 247 | 248 | async def _create_sender(self) -> MTProtoSender: 249 | dc = await self.client._get_dc(self.dc_id) 250 | sender = MTProtoSender(self.auth_key, loggers=self.client._log) 251 | await sender.connect( 252 | self.client._connection( 253 | dc.ip_address, 254 | dc.port, 255 | dc.id, 256 | loggers=self.client._log, 257 | proxy=self.client._proxy, 258 | ) 259 | ) 260 | if not self.auth_key: 261 | auth = await self.client(ExportAuthorizationRequest(self.dc_id)) 262 | self.client._init_request.query = ImportAuthorizationRequest( 263 | id=auth.id, bytes=auth.bytes 264 | ) 265 | req = InvokeWithLayerRequest(LAYER, self.client._init_request) 266 | await sender.send(req) 267 | self.auth_key = sender.auth_key 268 | return sender 269 | 270 | async def init_upload( 271 | self, 272 | file_id: int, 273 | file_size: int, 274 | part_size_kb: Optional[float] = None, 275 | connection_count: Optional[int] = None, 276 | ) -> Tuple[int, int, bool]: 277 | connection_count = connection_count or self._get_connection_count(file_size) 278 | part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024 279 | part_count = (file_size + part_size - 1) // part_size 280 | is_large = file_size > 10 * 1024 * 1024 281 | await self._init_upload(connection_count, file_id, part_count, is_large) 282 | return part_size, part_count, is_large 283 | 284 | async def upload(self, part: bytes) -> None: 285 | await self.senders[self.upload_ticker].next(part) 286 | self.upload_ticker = (self.upload_ticker + 1) % len(self.senders) 287 | 288 | async def finish_upload(self) -> None: 289 | await self._cleanup() 290 | 291 | async def download( 292 | self, 293 | file: TypeLocation, 294 | file_size: int, 295 | part_size_kb: Optional[float] = None, 296 | connection_count: Optional[int] = None, 297 | ) -> AsyncGenerator[bytes, None]: 298 | connection_count = connection_count or self._get_connection_count(file_size) 299 | part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024 300 | part_count = math.ceil(file_size / part_size) 301 | await self._init_download(connection_count, file, part_count, part_size) 302 | 303 | part = 0 304 | while part < part_count: 305 | tasks = [] 306 | for sender in self.senders: 307 | tasks.append(self.loop.create_task(sender.next())) 308 | for task in tasks: 309 | data = await task 310 | if not data: 311 | break 312 | yield data 313 | part += 1 314 | await self._cleanup() 315 | 316 | 317 | parallel_transfer_locks: DefaultDict[int, asyncio.Lock] = defaultdict( 318 | lambda: asyncio.Lock() 319 | ) 320 | 321 | 322 | def stream_file(file_to_stream: BinaryIO, chunk_size=1024): 323 | while True: 324 | data_read = file_to_stream.read(chunk_size) 325 | if not data_read: 326 | break 327 | yield data_read 328 | 329 | 330 | async def _internal_transfer_to_telegram( 331 | client: TelegramClient, response: BinaryIO, progress_callback: callable 332 | ) -> Tuple[TypeInputFile, int]: 333 | file_id = helpers.generate_random_long() 334 | file_size = os.path.getsize(response.name) 335 | 336 | hash_md5 = hashlib.md5() 337 | uploader = ParallelTransferrer(client) 338 | part_size, part_count, is_large = await uploader.init_upload(file_id, file_size) 339 | buffer = bytearray() 340 | for data in stream_file(response): 341 | if progress_callback: 342 | r = progress_callback(response.tell(), file_size) 343 | if inspect.isawaitable(r): 344 | try: 345 | await r 346 | except BaseException: 347 | pass 348 | if not is_large: 349 | hash_md5.update(data) 350 | if len(buffer) == 0 and len(data) == part_size: 351 | await uploader.upload(data) 352 | continue 353 | new_len = len(buffer) + len(data) 354 | if new_len >= part_size: 355 | cutoff = part_size - len(buffer) 356 | buffer.extend(data[:cutoff]) 357 | await uploader.upload(bytes(buffer)) 358 | buffer.clear() 359 | buffer.extend(data[cutoff:]) 360 | else: 361 | buffer.extend(data) 362 | if len(buffer) > 0: 363 | await uploader.upload(bytes(buffer)) 364 | await uploader.finish_upload() 365 | if is_large: 366 | return InputFileBig(file_id, part_count, filename), file_size 367 | else: 368 | return InputFile(file_id, part_count, filename, hash_md5.hexdigest()), file_size 369 | 370 | 371 | async def download_file( 372 | client: TelegramClient, 373 | location: TypeLocation, 374 | out: BinaryIO, 375 | progress_callback: callable = None, 376 | ) -> BinaryIO: 377 | size = location.size 378 | dc_id, location = utils.get_input_location(location) 379 | # We lock the transfers because telegram has connection count limits 380 | downloader = ParallelTransferrer(client, dc_id) 381 | downloaded = downloader.download(location, size) 382 | async for x in downloaded: 383 | out.write(x) 384 | if progress_callback: 385 | r = progress_callback(out.tell(), size) 386 | if inspect.isawaitable(r): 387 | try: 388 | await r 389 | except BaseException: 390 | pass 391 | 392 | return out 393 | 394 | 395 | async def upload_file( 396 | client: TelegramClient, 397 | file: BinaryIO, 398 | name, 399 | progress_callback: callable = None, 400 | ) -> TypeInputFile: 401 | global filename 402 | filename = name 403 | return (await _internal_transfer_to_telegram(client, file, progress_callback))[0] 404 | --------------------------------------------------------------------------------