├── 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 | [](https://heroku.com/deploy?template=https://github.com/Anshusharma75/TG-videoCompress)
15 |
16 | `Railway`
17 |
18 | [](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 |
--------------------------------------------------------------------------------