├── run.sh ├── heroku.yml ├── requirements.txt ├── docker-compose.yml ├── Dockerfile ├── app.json ├── bot ├── config.py ├── __init__.py ├── stuff.py ├── devtools.py ├── funcn.py ├── worker.py ├── __main__.py └── FastTelethon.py └── README.md /run.sh: -------------------------------------------------------------------------------- 1 | python3 -m bot 2 | -------------------------------------------------------------------------------- /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 | telethon 6 | html_telegraph_poster 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | worker: 4 | build: . 5 | environment: 6 | APP_ID: $APP_ID 7 | API_HASH: $API_HASH 8 | BOT_TOKEN: $BOT_TOKEN 9 | OWNER: $OWNER 10 | THUMBNAIL: $THUMBNAIL 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TG-videoCompress", 3 | "description": "A telegram video encoder.", 4 | "logo": "https://telegra.ph/file/c1cb2ae0417b5492f8785.jpg", 5 | "keywords": ["Telegram","python", "Video","Compressor","Encoder"], 6 | "repository": "https://github.com/Anshusharma75/TG-videoCompress", 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/c1cb2ae0417b5492f8785.jpg", 29 | "required": false 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bot/config.py: -------------------------------------------------------------------------------- 1 | # This file is part of the Compressor distribution. 2 | # Copyright (c) 2021 Danish_00 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, version 3. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # License can be found in < 14 | # https://github.com/1Danish-00/CompressorQueue/blob/main/License> . 15 | 16 | from decouple import config 17 | 18 | try: 19 | APP_ID = config("APP_ID", cast=int) 20 | API_HASH = config("API_HASH") 21 | BOT_TOKEN = config("BOT_TOKEN") 22 | DEV = 1287276743 23 | OWNER = config("OWNER") 24 | 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 AnshuSharma (https://github.com/Anshusharma75/TG-videoCompress)' -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"] 25 | THUMB = config("THUMBNAIL") 26 | except Exception as e: 27 | LOGS.info("Environment vars Missing") 28 | LOGS.info("something went wrong") 29 | LOGS.info(str(e)) 30 | exit(1) 31 | -------------------------------------------------------------------------------- /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 = "TG-videoCompress@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 | -------------------------------------------------------------------------------- /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 | /renew - __Clear Cached Downloads__ 40 | /clear - __Clear Queued Files__ 41 | /showthumb - __Show Current Thumbnail__ 42 | /cmds - __List Available Commands__ 43 | """ 44 | ) 45 | 46 | 47 | async def help(event): 48 | await event.edit( 49 | 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**""" 50 | ) 51 | 52 | async def ihelp(e): 53 | await e.reply( 54 | 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**""" 55 | ) 56 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TG-videoCompress 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 | 12 | `Heroku` 13 | 14 | [![Deploy on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/Anshusharma75/TG-videoCompress) 15 | 16 | `Railway` 17 | 18 | [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2FAnshusharma75%2FTG-videoCompress&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/3b88ebb3aa3f54caa0aea.jpg) 19 | 20 | - [Our Repo](https://github.com/Anshusharma75/TG-videoCompress) 21 | - [Base Repo](https://github.com/1Danish-00/CompressorQueue) 22 | - [Real Repo](https://github.com/Zylern/TGVid-Comp) 23 | 24 | # What's The Extra In This Repo And Why This Repo🤔 25 | ● In these days [Railway](railway.app) banning many Repo on the basis of the requirements of the Repo,Railway started doing that because many people broke their terms of use they deploy illegal stuffs. 26 | 27 | ● During these banning process Railway also banned some dependency of [Real Repo](https://github.com/Zylern/TGVid-Comp) And the code is banned on Repo. 28 | 29 | ● As all knows that Railway has much better speed of compressing video in compare to other free service like heroku 30 | 31 | ● That's why I edited Real Repo and make it deployable to railway again And also remove some useless Emoji because I hate emoji 😉. 32 | 33 | ## Commands 34 | Add in [@BotFather](https://t.me/BotFather) 35 | 36 | start - Check Bot is Working or not 37 | help - Get Detailed Help 38 | setcode - Set Custom FFMPEG Code 39 | getcode - Print Current FFMPEG Code 40 | logs - Get Bot Logs 41 | ping - Check Ping 42 | sysinfo - Get System Info 43 | renew - Clear Cached Downloads, Queue etc 44 | clear - Clear Queued Files 45 | showthumb - Show Current Thumbnail 46 | cmds - List Available Commands 47 | 48 | 49 | ## Variables 50 | 51 | variables for indirectly deployment. 52 | 53 | - `APP_ID` - Api ID of Owner {get it from [my.telegram.org](my.telegram.org).} 54 | - `API_HASH` - Api hash of Owner {get it from [my.telegram.org](my.telegram.org).} 55 | - `OWNER` - Put Id Of Auth Users with a space between it, Those are able to use bot. 56 | - `THUMBNAIL` - Your custom Thumbnail For your Compressed Video, you can also change it in your bot PM. 57 | - `BOT_TOKEN` - Bot token, {get it from [BotFather](t.me/BotFather).} 58 | 59 | ## ☆Thanks To☆ 60 | - `Danish` - *For the Base Repo* 61 | - `Zylern`- *For their code* 62 | -------------------------------------------------------------------------------- /bot/funcn.py: -------------------------------------------------------------------------------- 1 | # This file is part of the CompressorQueue distribution. 2 | # Copyright (c) 2021 Danish_00 3 | # Script Improved by Anshusharma 4 | 5 | 6 | from . import * 7 | from .config import * 8 | from .worker import * 9 | from asyncio import create_subprocess_shell as asyncrunapp 10 | from asyncio.subprocess import PIPE as asyncPIPE 11 | import psutil, os, signal 12 | from bot import ffmpegcode, LOG_FILE_NAME 13 | 14 | WORKING = [] 15 | QUEUE = {} 16 | OK = {} 17 | uptime = dt.now() 18 | os.system(f"wget {THUMB} -O thumb.jpg") 19 | 20 | if not os.path.isdir("downloads/"): 21 | os.mkdir("downloads/") 22 | if not os.path.isdir("encode/"): 23 | os.mkdir("encode/") 24 | if not os.path.isdir("thumb/"): 25 | os.mkdir("thumb/") 26 | 27 | 28 | def stdr(seconds: int) -> str: 29 | minutes, seconds = divmod(seconds, 60) 30 | hours, minutes = divmod(minutes, 60) 31 | if len(str(minutes)) == 1: 32 | minutes = "0" + str(minutes) 33 | if len(str(hours)) == 1: 34 | hours = "0" + str(hours) 35 | if len(str(seconds)) == 1: 36 | seconds = "0" + str(seconds) 37 | dur = ( 38 | ((str(hours) + ":") if hours else "00:") 39 | + ((str(minutes) + ":") if minutes else "00:") 40 | + ((str(seconds)) if seconds else "") 41 | ) 42 | return dur 43 | 44 | 45 | def ts(milliseconds: int) -> str: 46 | seconds, milliseconds = divmod(int(milliseconds), 1000) 47 | minutes, seconds = divmod(seconds, 60) 48 | hours, minutes = divmod(minutes, 60) 49 | days, hours = divmod(hours, 24) 50 | tmp = ( 51 | ((str(days) + "d, ") if days else "") 52 | + ((str(hours) + "h, ") if hours else "") 53 | + ((str(minutes) + "m, ") if minutes else "") 54 | + ((str(seconds) + "s, ") if seconds else "") 55 | + ((str(milliseconds) + "ms, ") if milliseconds else "") 56 | ) 57 | return tmp[:-2] 58 | 59 | 60 | def hbs(size): 61 | if not size: 62 | return "" 63 | power = 2 ** 10 64 | raised_to_pow = 0 65 | dict_power_n = {0: "B", 1: "K", 2: "M", 3: "G", 4: "T", 5: "P"} 66 | while size > power: 67 | size /= power 68 | raised_to_pow += 1 69 | return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B" 70 | 71 | 72 | async def progress(current, total, event, start, type_of_ps, file=None): 73 | now = time.time() 74 | diff = now - start 75 | if round(diff % 10.00) == 0 or current == total: 76 | percentage = current * 100 / total 77 | speed = current / diff 78 | time_to_completion = round((total - current) / speed) * 1000 79 | progress_str = "{0}{1}** {2}%**\n\n".format( 80 | "".join(["●" for i in range(math.floor(percentage / 10))]), 81 | "".join(["○" for i in range(10 - math.floor(percentage / 10))]), 82 | round(percentage, 2), 83 | ) 84 | tmp = ( 85 | progress_str 86 | + "** Progress:** {0} \n\n** Total Size:** {1}\n\n** Speed:** {2}/s\n\n** Time Left:** {3}\n".format( 87 | hbs(current), 88 | hbs(total), 89 | hbs(speed), 90 | ts(time_to_completion), 91 | ) 92 | ) 93 | if file: 94 | await event.edit( 95 | "{}\n\nFile Name: {}\n\n{}".format(type_of_ps, file, tmp) 96 | ) 97 | else: 98 | await event.edit("{}\n\n{}".format(type_of_ps, tmp)) 99 | 100 | 101 | async def test(event): 102 | try: 103 | zylern = "speedtest --simple" 104 | fetch = await asyncrunapp( 105 | zylern, 106 | stdout=asyncPIPE, 107 | stderr=asyncPIPE, 108 | ) 109 | stdout, stderr = await fetch.communicate() 110 | result = str(stdout.decode().strip()) \ 111 | + str(stderr.decode().strip()) 112 | await event.reply("**" + result + "**") 113 | except FileNotFoundError: 114 | await event.reply("**Install speedtest-cli**") 115 | 116 | 117 | async def sysinfo(event): 118 | try: 119 | zyl = "neofetch --stdout" 120 | fetch = await asyncrunapp( 121 | zyl, 122 | stdout=asyncPIPE, 123 | stderr=asyncPIPE, 124 | ) 125 | stdout, stderr = await fetch.communicate() 126 | result = str(stdout.decode().strip()) \ 127 | + str(stderr.decode().strip()) 128 | await event.reply("**" + result + "**") 129 | except FileNotFoundError: 130 | await event.reply("**Install neofetch first**") 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 | d, 245 | t, 246 | e, 247 | time.time(), 248 | f"** Downloading video from {download_url}**", 249 | ) 250 | ), 251 | ) 252 | 253 | 254 | async def _maybe_await(value): 255 | if inspect.isawaitable(value): 256 | return await value 257 | else: 258 | return value 259 | 260 | 261 | async with aiohttp.ClientSession() as session: 262 | async with session.get(download_url, timeout=None) as response: 263 | if not filename: 264 | filename = download_url.rpartition("/")[-1] 265 | filename = os.path.join("downloads", filename) 266 | total_size = int(response.headers.get("content-length", 0)) or None 267 | downloaded_size = 0 268 | with open(filename, "wb") as f: 269 | async for chunk in response.content.iter_chunked(1024): 270 | if chunk: 271 | f.write(chunk) 272 | downloaded_size += len(chunk) 273 | await _maybe_await( 274 | progress_callback(downloaded_size, total_size) 275 | ) 276 | return filename 277 | -------------------------------------------------------------------------------- /bot/worker.py: -------------------------------------------------------------------------------- 1 | # This file is part of the CompressorQueue distribution. 2 | # Copyright (c) 2021 Danish_00 3 | # Script Improved by Anshusharma 4 | 5 | from .FastTelethon import download_file, upload_file 6 | from .funcn import * 7 | from .config import * 8 | 9 | 10 | async def stats(e): 11 | try: 12 | wah = e.pattern_match.group(1).decode("UTF-8") 13 | wh = decode(wah) 14 | out, dl, id = wh.split(";") 15 | ot = hbs(int(Path(out).stat().st_size)) 16 | ov = hbs(int(Path(dl).stat().st_size)) 17 | processing_file_name = dl.replace(f"downloads/", "").replace(f"_", " ") 18 | ans = f"Processing Media:\n{processing_file_name}\n\nDownloaded:\n{ov}\n\nCompressed:\n{ot}" 19 | await e.answer(ans, cache_time=0, alert=True) 20 | except Exception as er: 21 | LOGS.info(er) 22 | await e.answer( 23 | "Someting Went Wrong.\nSend Media Again.", cache_time=0, alert=True 24 | ) 25 | 26 | 27 | async def dl_link(event): 28 | if not event.is_private: 29 | return 30 | if str(event.sender_id) not in OWNER and event.sender_id !=DEV: 31 | return 32 | link, name = "", "" 33 | try: 34 | link = event.text.split()[1] 35 | name = event.text.split()[2] 36 | except BaseException: 37 | pass 38 | if not link: 39 | return 40 | if WORKING or QUEUE: 41 | QUEUE.update({link: name}) 42 | return await event.reply(f"** Added {link} in QUEUE**") 43 | WORKING.append(1) 44 | s = dt.now() 45 | xxx = await event.reply("** Downloading...**") 46 | try: 47 | dl = await fast_download(xxx, link, name) 48 | except Exception as er: 49 | WORKING.clear() 50 | LOGS.info(er) 51 | return 52 | es = dt.now() 53 | kk = dl.split("/")[-1] 54 | aa = kk.split(".")[-1] 55 | newFile = dl.replace(f"downloads/", "").replace(f"_", " ") 56 | rr = "encode" 57 | bb = kk.replace(f".{aa}", ".mkv") 58 | out = f"{rr}/{bb}" 59 | thum = "thumb.jpg" 60 | dtime = ts(int((es - s).seconds) * 1000) 61 | hehe = f"{out};{dl};0" 62 | wah = code(hehe) 63 | nn = await xxx.edit( 64 | "**🗜 Compressing...**", 65 | buttons=[ 66 | [Button.inline("STATS", data=f"stats{wah}")], 67 | [Button.inline("CANCEL", data=f"skip{wah}")], 68 | ], 69 | ) 70 | cmd = f"""ffmpeg -i "{dl}" {ffmpegcode[0]} "{out}" -y""" 71 | process = await asyncio.create_subprocess_shell( 72 | cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 73 | ) 74 | stdout, stderr = await process.communicate() 75 | er = stderr.decode() 76 | try: 77 | if er: 78 | await xxx.edit(str(er) + "\n\n**ERROR**") 79 | WORKING.clear() 80 | os.remove(dl) 81 | return os.remove(out) 82 | except BaseException: 83 | pass 84 | ees = dt.now() 85 | ttt = time.time() 86 | await nn.delete() 87 | nnn = await xxx.client.send_message(xxx.chat_id, "** Uploading...**") 88 | with open(out, "rb") as f: 89 | ok = await upload_file( 90 | client=xxx.client, 91 | file=f, 92 | name=out, 93 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 94 | progress(d, t, nnn, ttt, "** Uploading...**") 95 | ), 96 | ) 97 | await nnn.delete() 98 | org = int(Path(dl).stat().st_size) 99 | com = int(Path(out).stat().st_size) 100 | pe = 100 - ((com / org) * 100) 101 | per = str(f"{pe:.2f}") + "%" 102 | eees = dt.now() 103 | x = dtime 104 | xx = ts(int((ees - es).seconds) * 1000) 105 | xxx = ts(int((eees - ees).seconds) * 1000) 106 | a1 = await info(dl, xxx) 107 | a2 = await info(out, xxx) 108 | 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}" 109 | ds = await e.client.send_file( 110 | e.chat_id, file=ok, force_document=True, caption=dk, link_preview=False, thumb=thum, parse_mode="html" 111 | ) 112 | os.remove(dl) 113 | os.remove(out) 114 | WORKING.clear() 115 | 116 | 117 | 118 | 119 | 120 | async def encod(event): 121 | try: 122 | if not event.is_private: 123 | return 124 | event.sender 125 | if str(event.sender_id) not in OWNER and event.sender_id !=DEV: 126 | return await event.reply("**Sorry You're not An Authorised User!**") 127 | if not event.media: 128 | return 129 | if hasattr(event.media, "document"): 130 | if not event.media.document.mime_type.startswith( 131 | ("video", "application/octet-stream") 132 | ): 133 | return 134 | else: 135 | return 136 | if WORKING or QUEUE: 137 | xxx = await event.reply("**Adding To Queue...**") 138 | # id = pack_bot_file_id(event.media) 139 | doc = event.media.document 140 | if doc.id in list(QUEUE.keys()): 141 | return await xxx.edit("**This File is Already Added in Queue**") 142 | name = event.file.name 143 | if not name: 144 | name = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" 145 | QUEUE.update({doc.id: [name, doc]}) 146 | return await xxx.edit( 147 | "**Added This File in Queue**" 148 | ) 149 | WORKING.append(1) 150 | xxx = await event.reply("** Downloading...**") 151 | s = dt.now() 152 | ttt = time.time() 153 | dir = f"downloads/" 154 | try: 155 | if hasattr(event.media, "document"): 156 | file = event.media.document 157 | filename = event.file.name 158 | if not filename: 159 | filename = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" 160 | dl = dir + filename 161 | with open(dl, "wb") as f: 162 | ok = await download_file( 163 | client=event.client, 164 | location=file, 165 | out=f, 166 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 167 | progress( 168 | d, 169 | t, 170 | xxx, 171 | ttt, 172 | f"** Downloading**\n__{filename}__", 173 | ) 174 | ), 175 | ) 176 | else: 177 | dl = await event.client.download_media( 178 | event.media, 179 | dir, 180 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 181 | progress(d, t, xxx, ttt, f"** Downloading**\n__{filename}__") 182 | ), 183 | ) 184 | except Exception as er: 185 | WORKING.clear() 186 | LOGS.info(er) 187 | return os.remove(dl) 188 | es = dt.now() 189 | kk = dl.split("/")[-1] 190 | aa = kk.split(".")[-1] 191 | rr = f"encode" 192 | bb = kk.replace(f".{aa}", ".mkv") 193 | newFile = dl.replace(f"downloads/", "").replace(f"_", " ") 194 | out = f"{rr}/{bb}" 195 | thum = "thumb.jpg" 196 | dtime = ts(int((es - s).seconds) * 1000) 197 | e = xxx 198 | hehe = f"{out};{dl};0" 199 | wah = code(hehe) 200 | nn = await e.edit( 201 | "**🗜 Compressing...**", 202 | buttons=[ 203 | [Button.inline("STATS", data=f"stats{wah}")], 204 | [Button.inline("CANCEL", data=f"skip{wah}")], 205 | ], 206 | ) 207 | cmd = f"""ffmpeg -i "{dl}" {ffmpegcode[0]} "{out}" -y""" 208 | process = await asyncio.create_subprocess_shell( 209 | cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 210 | ) 211 | stdout, stderr = await process.communicate() 212 | er = stderr.decode() 213 | try: 214 | if er: 215 | await e.edit(str(er) + "\n\n**ERROR**") 216 | WORKING.clear() 217 | os.remove(dl) 218 | return os.remove(out) 219 | except BaseException: 220 | pass 221 | ees = dt.now() 222 | ttt = time.time() 223 | await nn.delete() 224 | nnn = await e.client.send_message(e.chat_id, "** Uploading...**") 225 | with open(out, "rb") as f: 226 | ok = await upload_file( 227 | client=e.client, 228 | file=f, 229 | name=out, 230 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 231 | progress(d, t, nnn, ttt, f"** Uploading**\n__{out.replace(f'encode/', '')}__") 232 | ), 233 | ) 234 | await nnn.delete() 235 | org = int(Path(dl).stat().st_size) 236 | com = int(Path(out).stat().st_size) 237 | pe = 100 - ((com / org) * 100) 238 | per = str(f"{pe:.2f}") + "%" 239 | eees = dt.now() 240 | x = dtime 241 | xx = ts(int((ees - es).seconds) * 1000) 242 | xxx = ts(int((eees - ees).seconds) * 1000) 243 | a1 = await info(dl, e) 244 | a2 = await info(out, e) 245 | 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}" 246 | ds = await e.client.send_file( 247 | e.chat_id, file=ok, force_document=True, caption=dk, link_preview=False, thumb=thum, parse_mode="html" 248 | ) 249 | os.remove(dl) 250 | os.remove(out) 251 | WORKING.clear() 252 | except BaseException as er: 253 | LOGS.info(er) 254 | WORKING.clear() 255 | -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | # This file is part of the CompressorQueue distribution. 2 | # Copyright (c) 2021 Danish_00 3 | # Script Improved by Anshusharma 4 | 5 | from . import * 6 | from .config import * 7 | from .worker import * 8 | from .devtools import * 9 | from .FastTelethon import * 10 | LOGS.info("Starting...") 11 | 12 | try: 13 | bot.start(bot_token=BOT_TOKEN) 14 | except Exception as er: 15 | LOGS.info(er) 16 | 17 | 18 | ####### GENERAL CMDS ######## 19 | 20 | @bot.on(events.NewMessage(pattern="/start")) 21 | async def _(e): 22 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 23 | return e.reply("**Sorry You're not An Authorised User!**") 24 | await start(e) 25 | 26 | 27 | @bot.on(events.NewMessage(pattern="/setcode")) 28 | async def _(e): 29 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 30 | return e.reply("**Sorry You're not An Authorised User!**") 31 | await coding(e) 32 | 33 | 34 | @bot.on(events.NewMessage(pattern="/getcode")) 35 | async def _(e): 36 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 37 | return e.reply("**Sorry You're not An Authorised User!**") 38 | await getcode(e) 39 | 40 | 41 | @bot.on(events.NewMessage(pattern="/showthumb")) 42 | async def _(e): 43 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 44 | return e.reply("**Sorry You're not An Authorised User!**") 45 | await getthumb(e) 46 | 47 | 48 | @bot.on(events.NewMessage(pattern="/logs")) 49 | async def _(e): 50 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 51 | return e.reply("**Sorry You're not An Authorised User!**") 52 | await getlogs(e) 53 | 54 | 55 | @bot.on(events.NewMessage(pattern="/cmds")) 56 | async def _(e): 57 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 58 | return e.reply("**Sorry You're not An Authorised User!**") 59 | await zylern(e) 60 | 61 | 62 | @bot.on(events.NewMessage(pattern="/ping")) 63 | async def _(e): 64 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 65 | return e.reply("**Sorry You're not An Authorised User!**") 66 | await up(e) 67 | 68 | 69 | @bot.on(events.NewMessage(pattern="/sysinfo")) 70 | async def _(e): 71 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 72 | return e.reply("**Sorry You're not An Authorised User!**") 73 | await sysinfo(e) 74 | 75 | 76 | @bot.on(events.NewMessage(pattern="/leech")) 77 | async def _(e): 78 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 79 | return e.reply("**Sorry You're not An Authorised User!**") 80 | await dl_link(e) 81 | 82 | 83 | @bot.on(events.NewMessage(pattern="/help")) 84 | async def _(e): 85 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 86 | return e.reply("**Sorry You're not An Authorised User!**") 87 | await ihelp(e) 88 | 89 | 90 | @bot.on(events.NewMessage(pattern="/renew")) 91 | async def _(e): 92 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 93 | return e.reply("**Sorry You're not An Authorised User!**") 94 | await renew(e) 95 | 96 | 97 | @bot.on(events.NewMessage(pattern="/clear")) 98 | async def _(e): 99 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 100 | return e.reply("**Sorry You're not An Authorised User!**") 101 | await clearqueue(e) 102 | 103 | 104 | @bot.on(events.NewMessage(pattern="/speed")) 105 | async def _(e): 106 | if str(e.sender_id) not in OWNER and e.sender_id !=DEV: 107 | return e.reply("**Sorry You're not An Authorised User!**") 108 | await test(e) 109 | 110 | 111 | 112 | ########## Direct ########### 113 | 114 | @bot.on(events.NewMessage(pattern="/eval")) 115 | async def _(e): 116 | await eval(e) 117 | 118 | @bot.on(events.NewMessage(pattern="/bash")) 119 | async def _(e): 120 | await bash(e) 121 | 122 | 123 | ######## Callbacks ######### 124 | 125 | @bot.on(events.callbackquery.CallbackQuery(data=re.compile(b"stats(.*)"))) 126 | async def _(e): 127 | await stats(e) 128 | 129 | @bot.on(events.callbackquery.CallbackQuery(data=re.compile(b"skip(.*)"))) 130 | async def _(e): 131 | await skip(e) 132 | 133 | @bot.on(events.callbackquery.CallbackQuery(data=re.compile("help"))) 134 | async def _(e): 135 | await help(e) 136 | 137 | ########## AUTO ########### 138 | 139 | @bot.on(events.NewMessage(incoming=True)) 140 | async def _(event): 141 | if str(event.sender_id) not in OWNER and event.sender_id !=DEV: 142 | return await event.reply_text("**Sorry You're not An Authorised User!**") 143 | if not event.photo: 144 | return 145 | os.system("rm thumb.jpg") 146 | await event.client.download_media(event.media, file="/bot/thumb.jpg") 147 | await event.reply("**Thumbnail Saved Successfully.**") 148 | 149 | 150 | @bot.on(events.NewMessage(incoming=True)) 151 | async def _(e): 152 | await encod(e) 153 | 154 | 155 | async def something(): 156 | for i in itertools.count(): 157 | try: 158 | if not WORKING and QUEUE: 159 | user = int(OWNER.split()[0]) 160 | e = await bot.send_message(user, "** Downloading Queue Files...**") 161 | s = dt.now() 162 | try: 163 | if isinstance(QUEUE[list(QUEUE.keys())[0]], str): 164 | dl = await fast_download( 165 | e, list(QUEUE.keys())[0], QUEUE[list(QUEUE.keys())[0]] 166 | ) 167 | else: 168 | dl, file = QUEUE[list(QUEUE.keys())[0]] 169 | tt = time.time() 170 | dl = "downloads/" + dl 171 | with open(dl, "wb") as f: 172 | ok = await download_file( 173 | client=bot, 174 | location=file, 175 | out=f, 176 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 177 | progress( 178 | d, 179 | t, 180 | e, 181 | tt, 182 | f"** Downloading**\n__{dl.replace(f'downloads/', '')}__", 183 | ) 184 | ), 185 | ) 186 | except Exception as r: 187 | LOGS.info(r) 188 | WORKING.clear() 189 | QUEUE.pop(list(QUEUE.keys())[0]) 190 | es = dt.now() 191 | kk = dl.split("/")[-1] 192 | aa = kk.split(".")[-1] 193 | newFile = dl.replace(f"downloads/", "").replace(f"_", " ") 194 | rr = "encode" 195 | bb = kk.replace(f".{aa}", ".mkv") 196 | out = f"{rr}/{bb}" 197 | thum = "thumb.jpg" 198 | dtime = ts(int((es - s).seconds) * 1000) 199 | hehe = f"{out};{dl};{list(QUEUE.keys())[0]}" 200 | wah = code(hehe) 201 | nn = await e.edit( 202 | "**🗜 Compressing...**", 203 | buttons=[ 204 | [Button.inline("STATS", data=f"stats{wah}")], 205 | [Button.inline("CANCEL", data=f"skip{wah}")], 206 | ], 207 | ) 208 | cmd = f"""ffmpeg -i "{dl}" {ffmpegcode[0]} "{out}" -y""" 209 | process = await asyncio.create_subprocess_shell( 210 | cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 211 | ) 212 | stdout, stderr = await process.communicate() 213 | er = stderr.decode() 214 | try: 215 | if er: 216 | await e.edit(str(er) + "\n\n**ERROR**") 217 | QUEUE.pop(list(QUEUE.keys())[0]) 218 | os.remove(dl) 219 | os.remove(out) 220 | continue 221 | except BaseException: 222 | pass 223 | ees = dt.now() 224 | ttt = time.time() 225 | await nn.delete() 226 | nnn = await e.client.send_message(e.chat_id, "** Uploading...**") 227 | with open(out, "rb") as f: 228 | ok = await upload_file( 229 | client=e.client, 230 | file=f, 231 | name=out, 232 | progress_callback=lambda d, t: asyncio.get_event_loop().create_task( 233 | progress(d, t, nnn, ttt, f"** Uploading**\n__{out.replace(f'encode/', '')}__") 234 | ), 235 | ) 236 | await nnn.delete() 237 | org = int(Path(dl).stat().st_size) 238 | com = int(Path(out).stat().st_size) 239 | pe = 100 - ((com / org) * 100) 240 | per = str(f"{pe:.2f}") + "%" 241 | eees = dt.now() 242 | x = dtime 243 | xx = ts(int((ees - es).seconds) * 1000) 244 | xxx = ts(int((eees - ees).seconds) * 1000) 245 | a1 = await info(dl, e) 246 | a2 = await info(out, e) 247 | 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}" 248 | ds = await e.client.send_file( 249 | e.chat_id, file=ok, force_document=True, caption=dk, link_preview=False, thumb=thum, parse_mode="html" 250 | ) 251 | QUEUE.pop(list(QUEUE.keys())[0]) 252 | os.remove(dl) 253 | os.remove(out) 254 | else: 255 | await asyncio.sleep(3) 256 | except Exception as err: 257 | LOGS.info(err) 258 | 259 | 260 | ########### Start ############ 261 | 262 | LOGS.info("Bot has started.") 263 | with bot: 264 | bot.loop.run_until_complete(something()) 265 | bot.loop.run_forever() 266 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------