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