Spaces:
Paused
Paused
Upload 13 files
Browse files- .gitignore +178 -0
- Dockerfile +25 -0
- UpscalerBotV1.py +213 -0
- initialSetup.py +11 -0
- local.py +228 -0
- main(backup).py +285 -0
- main.py +336 -0
- readme.md +16 -0
- real-esrgan/Upscaling Notes.txt +17 -0
- requirements.txt +4 -0
- resources/mediaChannels.json +7 -0
- upscaler-bot.code-workspace +11 -0
- upscaler-docs.md +29 -0
.gitignore
ADDED
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Created by https://www.toptal.com/developers/gitignore/api/python
|
2 |
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
3 |
+
|
4 |
+
real-esrgan/
|
5 |
+
|
6 |
+
### Python ###
|
7 |
+
# Byte-compiled / optimized / DLL files
|
8 |
+
__pycache__/
|
9 |
+
*.py[cod]
|
10 |
+
*$py.class
|
11 |
+
|
12 |
+
# C extensions
|
13 |
+
*.so
|
14 |
+
|
15 |
+
# Distribution / packaging
|
16 |
+
.Python
|
17 |
+
build/
|
18 |
+
develop-eggs/
|
19 |
+
dist/
|
20 |
+
downloads/
|
21 |
+
eggs/
|
22 |
+
.eggs/
|
23 |
+
lib/
|
24 |
+
lib64/
|
25 |
+
parts/
|
26 |
+
sdist/
|
27 |
+
var/
|
28 |
+
wheels/
|
29 |
+
share/python-wheels/
|
30 |
+
*.egg-info/
|
31 |
+
.installed.cfg
|
32 |
+
*.egg
|
33 |
+
MANIFEST
|
34 |
+
|
35 |
+
# PyInstaller
|
36 |
+
# Usually these files are written by a python script from a template
|
37 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
38 |
+
*.manifest
|
39 |
+
*.spec
|
40 |
+
|
41 |
+
# Installer logs
|
42 |
+
pip-log.txt
|
43 |
+
pip-delete-this-directory.txt
|
44 |
+
|
45 |
+
# Unit test / coverage reports
|
46 |
+
htmlcov/
|
47 |
+
.tox/
|
48 |
+
.nox/
|
49 |
+
.coverage
|
50 |
+
.coverage.*
|
51 |
+
.cache
|
52 |
+
nosetests.xml
|
53 |
+
coverage.xml
|
54 |
+
*.cover
|
55 |
+
*.py,cover
|
56 |
+
.hypothesis/
|
57 |
+
.pytest_cache/
|
58 |
+
cover/
|
59 |
+
|
60 |
+
# Translations
|
61 |
+
*.mo
|
62 |
+
*.pot
|
63 |
+
|
64 |
+
# Django stuff:
|
65 |
+
*.log
|
66 |
+
local_settings.py
|
67 |
+
db.sqlite3
|
68 |
+
db.sqlite3-journal
|
69 |
+
|
70 |
+
# Flask stuff:
|
71 |
+
instance/
|
72 |
+
.webassets-cache
|
73 |
+
|
74 |
+
# Scrapy stuff:
|
75 |
+
.scrapy
|
76 |
+
|
77 |
+
# Sphinx documentation
|
78 |
+
docs/_build/
|
79 |
+
|
80 |
+
# PyBuilder
|
81 |
+
.pybuilder/
|
82 |
+
target/
|
83 |
+
|
84 |
+
# Jupyter Notebook
|
85 |
+
.ipynb_checkpoints
|
86 |
+
|
87 |
+
# IPython
|
88 |
+
profile_default/
|
89 |
+
ipython_config.py
|
90 |
+
|
91 |
+
# pyenv
|
92 |
+
# For a library or package, you might want to ignore these files since the code is
|
93 |
+
# intended to run in multiple environments; otherwise, check them in:
|
94 |
+
# .python-version
|
95 |
+
|
96 |
+
# pipenv
|
97 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
98 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
99 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
100 |
+
# install all needed dependencies.
|
101 |
+
#Pipfile.lock
|
102 |
+
|
103 |
+
# poetry
|
104 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
105 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
106 |
+
# commonly ignored for libraries.
|
107 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
108 |
+
#poetry.lock
|
109 |
+
|
110 |
+
# pdm
|
111 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
112 |
+
#pdm.lock
|
113 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
114 |
+
# in version control.
|
115 |
+
# https://pdm.fming.dev/#use-with-ide
|
116 |
+
.pdm.toml
|
117 |
+
|
118 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
119 |
+
__pypackages__/
|
120 |
+
|
121 |
+
# Celery stuff
|
122 |
+
celerybeat-schedule
|
123 |
+
celerybeat.pid
|
124 |
+
|
125 |
+
# SageMath parsed files
|
126 |
+
*.sage.py
|
127 |
+
|
128 |
+
# Environments
|
129 |
+
.env
|
130 |
+
.venv
|
131 |
+
env/
|
132 |
+
venv/
|
133 |
+
ENV/
|
134 |
+
env.bak/
|
135 |
+
venv.bak/
|
136 |
+
|
137 |
+
# Spyder project settings
|
138 |
+
.spyderproject
|
139 |
+
.spyproject
|
140 |
+
|
141 |
+
# Rope project settings
|
142 |
+
.ropeproject
|
143 |
+
|
144 |
+
# mkdocs documentation
|
145 |
+
/site
|
146 |
+
|
147 |
+
# mypy
|
148 |
+
.mypy_cache/
|
149 |
+
.dmypy.json
|
150 |
+
dmypy.json
|
151 |
+
|
152 |
+
# Pyre type checker
|
153 |
+
.pyre/
|
154 |
+
|
155 |
+
# pytype static type analyzer
|
156 |
+
.pytype/
|
157 |
+
|
158 |
+
# Cython debug symbols
|
159 |
+
cython_debug/
|
160 |
+
|
161 |
+
# PyCharm
|
162 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
163 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
164 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
165 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
166 |
+
#.idea/
|
167 |
+
|
168 |
+
### Python Patch ###
|
169 |
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
170 |
+
poetry.toml
|
171 |
+
|
172 |
+
# ruff
|
173 |
+
.ruff_cache/
|
174 |
+
|
175 |
+
# LSP config files
|
176 |
+
pyrightconfig.json
|
177 |
+
|
178 |
+
# End of https://www.toptal.com/developers/gitignore/api/python
|
Dockerfile
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
|
2 |
+
# you will also find guides on how best to write your Dockerfile
|
3 |
+
|
4 |
+
FROM python:3.11
|
5 |
+
|
6 |
+
WORKDIR /code
|
7 |
+
|
8 |
+
COPY ./requirements.txt /code/requirements.txt
|
9 |
+
|
10 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
11 |
+
|
12 |
+
RUN useradd -m -u 1000 user
|
13 |
+
|
14 |
+
USER user
|
15 |
+
|
16 |
+
ENV HOME=/home/user \
|
17 |
+
PATH=/home/user/.local/bin:$PATH
|
18 |
+
|
19 |
+
WORKDIR $HOME/app
|
20 |
+
|
21 |
+
COPY --chown=user . $HOME/app
|
22 |
+
|
23 |
+
RUN ["python", "initialSetup.py"]
|
24 |
+
|
25 |
+
CMD ["python", "main.py"]
|
UpscalerBotV1.py
ADDED
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, datetime, pytz, discord, shutil, requests, subprocess
|
2 |
+
from discord.ext import commands
|
3 |
+
from time import perf_counter
|
4 |
+
|
5 |
+
CLIENT_SECRET = "Ca3tbHTsbImaDmlzXqnpUQLONYtBY1eA"
|
6 |
+
TOKEN = "OTE5ODk0NDEwODkwNzI3NDg1.G96t2g.XV5efYvSNKqsNvPFxwmviLi-oY6oAYVPTzV78A"
|
7 |
+
ADMINS = [766145655038410763, 937495666471628831]
|
8 |
+
basepath = r"C:\Users\Pawin\Software\real-esrgan\discordbot"
|
9 |
+
|
10 |
+
def getloggingtime():
|
11 |
+
return datetime.datetime.now(pytz.timezone('Asia/Bangkok')).strftime('%d/%m/%Y %H:%M:%S')
|
12 |
+
|
13 |
+
def generate_id(path):
|
14 |
+
return str(len(os.listdir(path))+1).zfill(3)
|
15 |
+
|
16 |
+
def file_cleanup():
|
17 |
+
dirlist = [fr"{basepath}\input", fr"{basepath}\output"]
|
18 |
+
for directory in dirlist:
|
19 |
+
shutil.rmtree(directory)
|
20 |
+
os.mkdir(directory)
|
21 |
+
print("BOT: Sucessfully cleaned directories")
|
22 |
+
|
23 |
+
file_cleanup()
|
24 |
+
|
25 |
+
client = commands.Bot(
|
26 |
+
command_prefix = "!",
|
27 |
+
case_insensitive = True,
|
28 |
+
help_command = None,
|
29 |
+
activity = discord.Streaming(name = f"/help | Local Instance", url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
30 |
+
)
|
31 |
+
|
32 |
+
"""BOT EVENTS"""
|
33 |
+
#Show Status When Bot Is Ready
|
34 |
+
@client.event
|
35 |
+
async def on_ready():
|
36 |
+
print('BOT: We have successfully logged in as {0.user}\n'.format(client))
|
37 |
+
|
38 |
+
|
39 |
+
@client.event
|
40 |
+
async def on_message(message):
|
41 |
+
msg = str(message.content)
|
42 |
+
validMediaChannels = [1083235916354703430, 896342418859892736, 915795164721709077]
|
43 |
+
channel = message.channel
|
44 |
+
author = message.author
|
45 |
+
|
46 |
+
channelId = int(channel.id)
|
47 |
+
authorId = int(author.id)
|
48 |
+
#print(f"Channel id is {channelId}, author id is {authorId}")
|
49 |
+
|
50 |
+
imageList = []
|
51 |
+
for item in message.attachments:
|
52 |
+
if item.content_type.startswith("image"):
|
53 |
+
imageList.append(item.url)
|
54 |
+
|
55 |
+
if message.author.bot or msg.startswith("p!"):
|
56 |
+
pass
|
57 |
+
|
58 |
+
elif (authorId in ADMINS) and (channelId in validMediaChannels) and (message.attachments) and (imageList):
|
59 |
+
print(f'IMAGE: New attachment recieved from {author} in channel {channel}. Processing...')
|
60 |
+
jobReceiptMessage = await message.channel.send(f"\nUpscale job received for message {message.id}")
|
61 |
+
jobStatusMessage = await message.channel.send(f"Status: Starting upscale.")
|
62 |
+
|
63 |
+
|
64 |
+
attachmentCount = len(imageList)
|
65 |
+
multipleImages = attachmentCount > 1
|
66 |
+
if multipleImages:
|
67 |
+
batchstart = perf_counter()
|
68 |
+
taskType = "Batch Upscale"
|
69 |
+
else:
|
70 |
+
taskType = "Image Upscaling"
|
71 |
+
|
72 |
+
for i in range(attachmentCount):
|
73 |
+
|
74 |
+
"""PREPROCESSING"""
|
75 |
+
fileid = f"{message.id}_{i}"
|
76 |
+
statusTemplate = f"__Status__: %CURRENTSTEP% image from message *{message.id}* **({i+1}/{attachmentCount})**."
|
77 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "🔄 Preprocessing"))
|
78 |
+
|
79 |
+
starttime = perf_counter()
|
80 |
+
url = imageList[i]
|
81 |
+
|
82 |
+
extension = url.split(".")[-1]
|
83 |
+
#fileid = generate_id(f"{basepath}\input")
|
84 |
+
|
85 |
+
inputfile = fr"{basepath}\input\{fileid}.{extension}"
|
86 |
+
outputfile = fr"{basepath}\output\{fileid}.png"
|
87 |
+
|
88 |
+
with open (inputfile, "wb") as f:
|
89 |
+
f.write(requests.get(url).content)
|
90 |
+
|
91 |
+
|
92 |
+
|
93 |
+
"""UPSCALE CONFIG"""
|
94 |
+
ai_model = "realesrgan-x4plus-anime"
|
95 |
+
#Can be realesr-animevideov3 (default) | realesrgan-x4plus | realesrgan-x4plus-anime | realesrnet-x4plus
|
96 |
+
if ai_model == "realesr-animevideov3":
|
97 |
+
scale = "2"
|
98 |
+
tilesize = "768"
|
99 |
+
resizeRequired = False
|
100 |
+
else:
|
101 |
+
scale = "4"
|
102 |
+
tilesize = "256"
|
103 |
+
resizeRequired = True
|
104 |
+
|
105 |
+
#Scale 2 only works with the default model
|
106 |
+
execute_upscale = fr"C:\Users\Pawin\Software\real-esrgan\realesrgan-ncnn-vulkan.exe -i {inputfile} -o {outputfile} -s {scale} -n {ai_model} -f png -t {tilesize} -j 1:1:1"
|
107 |
+
|
108 |
+
|
109 |
+
|
110 |
+
"""UPSCALING"""
|
111 |
+
pendtime = perf_counter()
|
112 |
+
preprocessingtime = round((pendtime-starttime), 2)
|
113 |
+
print(f"PREPROCESS: Completed in {preprocessingtime}s.\nUPSCALE:")
|
114 |
+
await jobStatusMessage.edit(content=(statusTemplate.replace("%CURRENTSTEP%", "🧠 Upscaling")+"\nThis may take a while..."))
|
115 |
+
#os.system(execute_upscale)
|
116 |
+
subprocess.run(execute_upscale, shell=True)
|
117 |
+
|
118 |
+
uendtime = perf_counter()
|
119 |
+
|
120 |
+
upscaletime = round((uendtime-pendtime),2)
|
121 |
+
print(f"UPSCALE: Completed in {upscaletime}s.")
|
122 |
+
|
123 |
+
|
124 |
+
|
125 |
+
"""RESIZING"""
|
126 |
+
if resizeRequired:
|
127 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "⚙️ Resizing"))
|
128 |
+
subprocess.run(f"mogrify -resize 50% {outputfile}", shell=True)
|
129 |
+
rendtime = perf_counter()
|
130 |
+
resizingtime = round((rendtime-uendtime), 2)
|
131 |
+
print(f"RESIZE: Completed in {resizingtime}s.")
|
132 |
+
else:
|
133 |
+
resizingtime = 0
|
134 |
+
|
135 |
+
|
136 |
+
|
137 |
+
"""DELIVERING"""
|
138 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "✉️ Sending"))
|
139 |
+
|
140 |
+
try:
|
141 |
+
file=discord.File(outputfile)
|
142 |
+
imgurl = f"attachment://{fileid}.png"
|
143 |
+
prepembed=discord.Embed(title="Sucessfully Upscaled Image")
|
144 |
+
prepembed.set_image(url=imgurl)
|
145 |
+
|
146 |
+
jobResultMessage = await message.channel.send(embed=prepembed, file=file)
|
147 |
+
|
148 |
+
outputImageUrl = jobResultMessage.embeds[0].image.url
|
149 |
+
#outputImageUrl = jobResultMessage.attachments[0].url
|
150 |
+
|
151 |
+
#print(outputImageUrl)
|
152 |
+
embed = discord.Embed(title="Upscaled Image", url=outputImageUrl)
|
153 |
+
embed.set_author(name=author, icon_url=message.author.avatar_url)
|
154 |
+
embed.set_image(url=imgurl)
|
155 |
+
processingstats = f"Preprocessing took {preprocessingtime}s | Resizing took {resizingtime}s."
|
156 |
+
embed.set_footer(text=f"Took {upscaletime}s to upscale {fileid}.\nUpscaling done with {ai_model}.\n{processingstats}")
|
157 |
+
|
158 |
+
await jobResultMessage.edit(embed=embed)
|
159 |
+
|
160 |
+
except discord.errors.HTTPException as e:
|
161 |
+
baseErrorMessage = f"There was an error sending the output for image id {fileid}."
|
162 |
+
if '413 Payload Too Large' in str(e):
|
163 |
+
await message.channel.send(f"{baseErrorMessage} It was probably too large for discord to handle.\n```python\n{e}```")
|
164 |
+
else:
|
165 |
+
await message.channel.send(f"{baseErrorMessage}\n```python\n{e}```")
|
166 |
+
|
167 |
+
except Exception as e:
|
168 |
+
await message.channel.send(f"Encountered an error while processing attachment {fileid}\n```python\n{e}```")
|
169 |
+
|
170 |
+
|
171 |
+
|
172 |
+
"""CLEANING UP"""
|
173 |
+
#Already finished the whole job.
|
174 |
+
await jobStatusMessage.edit(content=f"\n__Status__: ✅ Job Completed for all {attachmentCount} attachments from message {message.id} at {getloggingtime()}".replace("all 1 attachments", "an attachment"))
|
175 |
+
await message.delete()
|
176 |
+
await jobReceiptMessage.delete()
|
177 |
+
|
178 |
+
|
179 |
+
print(f"IMAGE: Task completed for messageID {message.id}\n")
|
180 |
+
|
181 |
+
|
182 |
+
else:
|
183 |
+
print(f"MESSAGE: {channel}- {author}: {msg}")
|
184 |
+
await client.process_commands(message)
|
185 |
+
|
186 |
+
|
187 |
+
"""BOT COMMANDS"""
|
188 |
+
|
189 |
+
@client.command()
|
190 |
+
async def test(ctx):
|
191 |
+
await ctx.send("Hello!")
|
192 |
+
|
193 |
+
@client.command()
|
194 |
+
async def ping(ctx):
|
195 |
+
await ctx.send(f'The ping is {round(client.latency * 1000)}ms.')
|
196 |
+
|
197 |
+
@client.command()
|
198 |
+
async def clear(ctx, amount=None):
|
199 |
+
if ctx.author.id in ADMINS:
|
200 |
+
if amount == "all":
|
201 |
+
amount = 100
|
202 |
+
elif amount == None:
|
203 |
+
amount = 2
|
204 |
+
else:
|
205 |
+
amount = int(amount)
|
206 |
+
await ctx.channel.purge(limit=amount)
|
207 |
+
await ctx.send(f"Finished clearing {amount} messages {ctx.message.author.mention}", delete_after=4)
|
208 |
+
else:
|
209 |
+
await ctx.send('You do not have the permissions to to clear messages using this bot.')
|
210 |
+
|
211 |
+
logmsg = f"BOT: Attempting to starting the bot at {getloggingtime()} GMT+7"
|
212 |
+
print(logmsg)
|
213 |
+
client.run(TOKEN)
|
initialSetup.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, requests, zipfile, io, subprocess
|
2 |
+
|
3 |
+
os.makedirs('./real-esrgan/discordbot/input', exist_ok=True)
|
4 |
+
os.makedirs('./real-esrgan/discordbot/output', exist_ok=True)
|
5 |
+
|
6 |
+
|
7 |
+
with requests.get('https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesrgan-ncnn-vulkan-20220424-ubuntu.zip') as r:
|
8 |
+
zip_file = zipfile.ZipFile(io.BytesIO(r.content))
|
9 |
+
zip_file.extractall('./real-esrgan')
|
10 |
+
|
11 |
+
subprocess.run(['chmod', '+x', './real-esrgan/realesrgan-ncnn-vulkan'])
|
local.py
ADDED
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, datetime, pytz, discord, shutil, requests, subprocess, asyncio
|
2 |
+
from discord.ext import commands
|
3 |
+
from time import perf_counter
|
4 |
+
|
5 |
+
CLIENT_SECRET = "Ca3tbHTsbImaDmlzXqnpUQLONYtBY1eA"
|
6 |
+
TOKEN = "OTE5ODk0NDEwODkwNzI3NDg1.G96t2g.XV5efYvSNKqsNvPFxwmviLi-oY6oAYVPTzV78A"
|
7 |
+
ADMINS = [766145655038410763, 937495666471628831]
|
8 |
+
basepath = r"C:\Users\Pawin\Software\real-esrgan\discordbot"
|
9 |
+
|
10 |
+
def getloggingtime():
|
11 |
+
return datetime.datetime.now(pytz.timezone('Asia/Bangkok')).strftime('%d/%m/%Y %H:%M:%S')
|
12 |
+
|
13 |
+
def generate_id(path):
|
14 |
+
return str(len(os.listdir(path))+1).zfill(3)
|
15 |
+
|
16 |
+
def file_cleanup():
|
17 |
+
dirlist = [fr"{basepath}\input", fr"{basepath}\output"]
|
18 |
+
for directory in dirlist:
|
19 |
+
shutil.rmtree(directory)
|
20 |
+
os.mkdir(directory)
|
21 |
+
print("BOT: Sucessfully cleaned directories")
|
22 |
+
|
23 |
+
file_cleanup()
|
24 |
+
|
25 |
+
client = commands.Bot(
|
26 |
+
command_prefix = "!",
|
27 |
+
case_insensitive = True,
|
28 |
+
help_command = None,
|
29 |
+
activity = discord.Streaming(name = f"/help | Local Instance", url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
30 |
+
)
|
31 |
+
|
32 |
+
"""BOT EVENTS"""
|
33 |
+
#Show Status When Bot Is Ready
|
34 |
+
@client.event
|
35 |
+
async def on_ready():
|
36 |
+
print('BOT: We have successfully logged in as {0.user}\n'.format(client))
|
37 |
+
|
38 |
+
CurrentlyProcessingJob = False
|
39 |
+
|
40 |
+
@client.event
|
41 |
+
async def on_message(message):
|
42 |
+
global CurrentlyProcessingJob
|
43 |
+
|
44 |
+
msg = str(message.content)
|
45 |
+
validMediaChannels = [1083235916354703430, 896342418859892736, 915795164721709077]
|
46 |
+
channel = message.channel
|
47 |
+
author = message.author
|
48 |
+
|
49 |
+
channelId = int(channel.id)
|
50 |
+
authorId = int(author.id)
|
51 |
+
#print(f"Channel id is {channelId}, author id is {authorId}")
|
52 |
+
|
53 |
+
imageList = []
|
54 |
+
for item in message.attachments:
|
55 |
+
if item.content_type.startswith("image"):
|
56 |
+
imageList.append(item.url)
|
57 |
+
|
58 |
+
if message.author.bot or msg.startswith("p!"):
|
59 |
+
pass
|
60 |
+
|
61 |
+
elif (authorId in ADMINS) and (channelId in validMediaChannels) and (message.attachments) and (imageList):
|
62 |
+
print(f'IMAGE: New attachment recieved from {author} in channel {channel}. Processing...')
|
63 |
+
|
64 |
+
jobReceiptMessage = await message.reply(f"\nUpscale job received for message {message.id}", mention_author=False)
|
65 |
+
jobStatusMessage = await message.reply(f"Status: Upscale queued.", mention_author=False)
|
66 |
+
|
67 |
+
if CurrentlyProcessingJob:
|
68 |
+
pendingTemplate = f"__Status__: Upscale %CURRENTSTEP% for job *{message.id}*"
|
69 |
+
await jobStatusMessage.edit(content=pendingTemplate.replace("%CURRENTSTEP%", "Pending 🕰️"))
|
70 |
+
print(f"IMAGE: There is already a job running. Message id {message.id} has been put on queue")
|
71 |
+
while CurrentlyProcessingJob:
|
72 |
+
await asyncio.sleep(1)
|
73 |
+
#wait untill it finishes
|
74 |
+
await jobStatusMessage.edit(content=pendingTemplate.replace("%CURRENTSTEP%", "Starting 🏃"))
|
75 |
+
print(f"IMAGE: Starting job {message.id}")
|
76 |
+
CurrentlyProcessingJob = True
|
77 |
+
|
78 |
+
attachmentCount = len(imageList)
|
79 |
+
multipleImages = attachmentCount > 1
|
80 |
+
if multipleImages:
|
81 |
+
batchstart = perf_counter()
|
82 |
+
taskType = "Batch Upscale"
|
83 |
+
else:
|
84 |
+
taskType = "Image Upscaling"
|
85 |
+
|
86 |
+
for i in range(attachmentCount):
|
87 |
+
|
88 |
+
"""PREPROCESSING"""
|
89 |
+
fileid = f"{message.id}_{i}"
|
90 |
+
statusTemplate = f"__Status__: %CURRENTSTEP% image from message *{message.id}* **({i+1}/{attachmentCount})**."
|
91 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "🔄 Preprocessing"))
|
92 |
+
|
93 |
+
starttime = perf_counter()
|
94 |
+
url = imageList[i]
|
95 |
+
|
96 |
+
extension = url.split(".")[-1]
|
97 |
+
#fileid = generate_id(f"{basepath}\input")
|
98 |
+
|
99 |
+
inputfile = fr"{basepath}\input\{fileid}.{extension}"
|
100 |
+
outputfile = fr"{basepath}\output\{fileid}.png"
|
101 |
+
|
102 |
+
with open (inputfile, "wb") as f:
|
103 |
+
f.write(requests.get(url).content)
|
104 |
+
|
105 |
+
|
106 |
+
|
107 |
+
"""UPSCALE CONFIG"""
|
108 |
+
ai_model = "realesrgan-x4plus-anime"
|
109 |
+
#Can be realesr-animevideov3 (default) | realesrgan-x4plus | realesrgan-x4plus-anime | realesrnet-x4plus
|
110 |
+
if ai_model == "realesr-animevideov3":
|
111 |
+
scale = "2"
|
112 |
+
tilesize = "768"
|
113 |
+
resizeRequired = False
|
114 |
+
else:
|
115 |
+
scale = "4"
|
116 |
+
tilesize = "256"
|
117 |
+
resizeRequired = True
|
118 |
+
|
119 |
+
#Scale 2 only works with the default model
|
120 |
+
execute_upscale = fr"C:\Users\Pawin\Software\real-esrgan\realesrgan-ncnn-vulkan.exe -i {inputfile} -o {outputfile} -s {scale} -n {ai_model} -f png -t {tilesize} -j 1:1:1"
|
121 |
+
|
122 |
+
|
123 |
+
|
124 |
+
"""UPSCALING"""
|
125 |
+
pendtime = perf_counter()
|
126 |
+
preprocessingtime = round((pendtime-starttime), 2)
|
127 |
+
print(f"PREPROCESS: Completed in {preprocessingtime}s.\nUPSCALE:")
|
128 |
+
await jobStatusMessage.edit(content=(statusTemplate.replace("%CURRENTSTEP%", "🧠 Upscaling")+"\nThis may take a while..."))
|
129 |
+
#os.system(execute_upscale)
|
130 |
+
subprocess.run(execute_upscale, shell=True)
|
131 |
+
|
132 |
+
uendtime = perf_counter()
|
133 |
+
|
134 |
+
upscaletime = round((uendtime-pendtime),2)
|
135 |
+
print(f"UPSCALE: Completed in {upscaletime}s.")
|
136 |
+
|
137 |
+
|
138 |
+
|
139 |
+
"""RESIZING"""
|
140 |
+
if resizeRequired:
|
141 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "⚙️ Resizing"))
|
142 |
+
subprocess.run(f"mogrify -resize 50% {outputfile}", shell=True)
|
143 |
+
rendtime = perf_counter()
|
144 |
+
resizingtime = round((rendtime-uendtime), 2)
|
145 |
+
print(f"RESIZE: Completed in {resizingtime}s.")
|
146 |
+
else:
|
147 |
+
resizingtime = 0
|
148 |
+
|
149 |
+
|
150 |
+
|
151 |
+
"""DELIVERING"""
|
152 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "✉️ Sending"))
|
153 |
+
|
154 |
+
try:
|
155 |
+
#file=discord.File(outputfile)
|
156 |
+
localImageUrl = f"http://thinkpad.pawin.tk/upscale/{fileid}.png"
|
157 |
+
|
158 |
+
|
159 |
+
#outputImageUrl = jobResultMessage.embeds[0].image.url
|
160 |
+
#uncomment above to use outside local network
|
161 |
+
#outputImageUrl = jobResultMessage.attachments[0].url
|
162 |
+
|
163 |
+
#print(outputImageUrl)
|
164 |
+
embed = discord.Embed(title="Upscaled Image", url=localImageUrl, description="Local mode is on. Please use the link above for upscaled image.")
|
165 |
+
embed.set_author(name=author, icon_url=message.author.avatar_url)
|
166 |
+
embed.set_image(url=url)
|
167 |
+
processingstats = f"Preprocessing took {preprocessingtime}s | Resizing took {resizingtime}s."
|
168 |
+
embed.set_footer(text=f"Took {upscaletime}s to upscale {fileid}.\nUpscaling done with {ai_model}.\n{processingstats}")
|
169 |
+
|
170 |
+
await message.channel.send(embed=embed)
|
171 |
+
|
172 |
+
except discord.errors.HTTPException as e:
|
173 |
+
baseErrorMessage = f"There was an error sending the output for image id {fileid}."
|
174 |
+
if '413 Payload Too Large' in str(e):
|
175 |
+
await message.reply(f"{baseErrorMessage} It was probably too large for discord to handle.\n```python\n{e}```")
|
176 |
+
else:
|
177 |
+
await message.reply(f"{baseErrorMessage}\n```python\n{e}```")
|
178 |
+
|
179 |
+
except Exception as e:
|
180 |
+
await message.reply(f"Encountered an error while processing attachment {fileid}\n```python\n{e}```")
|
181 |
+
|
182 |
+
|
183 |
+
|
184 |
+
"""CLEANING UP"""
|
185 |
+
#Already finished the whole job.
|
186 |
+
await jobStatusMessage.edit(content=f"\n__Status__: ✅ Job Completed for all {attachmentCount} attachments from message {message.id} at {getloggingtime()}".replace("all 1 attachments", "an attachment"), mention_author=False)
|
187 |
+
await message.delete()
|
188 |
+
await jobReceiptMessage.delete()
|
189 |
+
await asyncio.sleep(2)
|
190 |
+
await jobStatusMessage.delete()
|
191 |
+
|
192 |
+
|
193 |
+
|
194 |
+
print(f"IMAGE: Job finished for messageID {message.id}\n")
|
195 |
+
CurrentlyProcessingJob = False
|
196 |
+
|
197 |
+
else:
|
198 |
+
print(f"MESSAGE: {channel}- {author}: {msg}")
|
199 |
+
await client.process_commands(message)
|
200 |
+
|
201 |
+
|
202 |
+
"""BOT COMMANDS"""
|
203 |
+
|
204 |
+
@client.command()
|
205 |
+
async def test(ctx):
|
206 |
+
await ctx.send("Hello!")
|
207 |
+
|
208 |
+
@client.command()
|
209 |
+
async def ping(ctx):
|
210 |
+
await ctx.send(f'The ping is {round(client.latency * 1000)}ms.')
|
211 |
+
|
212 |
+
@client.command()
|
213 |
+
async def clear(ctx, amount=None):
|
214 |
+
if ctx.author.id in ADMINS:
|
215 |
+
if amount == "all":
|
216 |
+
amount = 100
|
217 |
+
elif amount == None:
|
218 |
+
amount = 2
|
219 |
+
else:
|
220 |
+
amount = int(amount)
|
221 |
+
await ctx.channel.purge(limit=amount)
|
222 |
+
await ctx.send(f"Finished clearing {amount} messages {ctx.message.author.mention}", delete_after=4)
|
223 |
+
else:
|
224 |
+
await ctx.send('You do not have the permissions to to clear messages using this bot.')
|
225 |
+
|
226 |
+
logmsg = f"BOT: Attempting to starting the bot at {getloggingtime()} GMT+7"
|
227 |
+
print(logmsg)
|
228 |
+
client.run(TOKEN)
|
main(backup).py
ADDED
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, datetime, pytz, discord, shutil, requests, subprocess, asyncio, platform
|
2 |
+
from discord.ext import commands
|
3 |
+
from time import perf_counter, sleep
|
4 |
+
from threading import Thread
|
5 |
+
|
6 |
+
CLIENT_SECRET = "Ca3tbHTsbImaDmlzXqnpUQLONYtBY1eA"
|
7 |
+
TOKEN = "OTE5ODk0NDEwODkwNzI3NDg1.G96t2g.XV5efYvSNKqsNvPFxwmviLi-oY6oAYVPTzV78A"
|
8 |
+
ADMINS = [766145655038410763, 937495666471628831]
|
9 |
+
|
10 |
+
ostype = platform.system()
|
11 |
+
print(f"\nDetected OS: {ostype}")
|
12 |
+
if ostype == "Windows":
|
13 |
+
basepath = r"C:\Users\Pawin\Software\real-esrgan"
|
14 |
+
executablepath = fr"{basepath}\realesrgan-ncnn-vulkan.exe"
|
15 |
+
webserverpath = r"C:\Users\Pawin\Code\local-server\main.py"
|
16 |
+
startWScmd = f"python {webserverpath}"
|
17 |
+
serverurl = "http://thinkpad.pawin.tk/upscale"
|
18 |
+
|
19 |
+
elif ostype == "Linux":
|
20 |
+
basepath = r"/home/pawin/Software/real-esrgan"
|
21 |
+
executablepath = fr"{basepath}/realesrgan-ncnn-vulkan"
|
22 |
+
webserverpath = r"/media/pawin/Windows/Users/Pawin/Code/local-server/main.py"
|
23 |
+
startWScmd = f'python3 {webserverpath}'
|
24 |
+
serverurl = "http://thinkpad.pawin.tk:8080/upscale"
|
25 |
+
|
26 |
+
imagepath = os.path.join(basepath, "discordbot")
|
27 |
+
inputpath = os.path.join(imagepath, "input")
|
28 |
+
outputpath = os.path.join(imagepath, "output")
|
29 |
+
|
30 |
+
|
31 |
+
outext = "jpg" #or png
|
32 |
+
#ai_model = "realesr-animevideov3"
|
33 |
+
ai_model = "realesrgan-x4plus"
|
34 |
+
#Can be realesr-animevideov3 (default) | realesrgan-x4plus | realesrgan-x4plus-anime | realesrnet-x4plus
|
35 |
+
|
36 |
+
|
37 |
+
def getloggingtime():
|
38 |
+
return datetime.datetime.now(pytz.timezone('Asia/Bangkok')).strftime('%d/%m/%Y %H:%M:%S')
|
39 |
+
|
40 |
+
def generate_id(path):
|
41 |
+
return str(len(os.listdir(path))+1).zfill(3)
|
42 |
+
|
43 |
+
def file_cleanup():
|
44 |
+
dirlist = [inputpath, outputpath]
|
45 |
+
contentlength = len(os.listdir(inputpath))
|
46 |
+
for directory in dirlist:
|
47 |
+
shutil.rmtree(directory)
|
48 |
+
os.mkdir(directory)
|
49 |
+
print("BOT: Sucessfully cleaned directories")
|
50 |
+
return contentlength
|
51 |
+
|
52 |
+
def start_webserver():
|
53 |
+
if webserverStartEnabled:
|
54 |
+
try:
|
55 |
+
print("Calling Webserver...")
|
56 |
+
subprocess.run(startWScmd, shell=True)
|
57 |
+
except:
|
58 |
+
print("Something went wrong with the webserver.")
|
59 |
+
print("Webserver process launched.")
|
60 |
+
else:
|
61 |
+
print("Webserver Start Skipped. If this is a local version, please run it manually.")
|
62 |
+
|
63 |
+
print(f"Starting the bot...")
|
64 |
+
webserverStartEnabled = input("Do you want to start the webserver also? (y/N): ").lower() == "y"
|
65 |
+
print(f"BTW, automatic directory cleaning have been disabled. To clean directories, please run !cleandir")
|
66 |
+
|
67 |
+
client = commands.Bot(
|
68 |
+
command_prefix = "!",
|
69 |
+
case_insensitive = True,
|
70 |
+
help_command = None,
|
71 |
+
activity = discord.Streaming(name = f"/help | Local Instance", url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
72 |
+
)
|
73 |
+
|
74 |
+
"""BOT EVENTS"""
|
75 |
+
#Show Status When Bot Is Ready
|
76 |
+
@client.event
|
77 |
+
async def on_ready():
|
78 |
+
print('BOT: We have successfully logged in as {0.user}\n'.format(client))
|
79 |
+
Thread(target=start_webserver, daemon=True).start()
|
80 |
+
|
81 |
+
CurrentlyProcessingJob = False
|
82 |
+
|
83 |
+
@client.event
|
84 |
+
async def on_message(message):
|
85 |
+
global CurrentlyProcessingJob
|
86 |
+
|
87 |
+
msg = str(message.content)
|
88 |
+
validMediaChannels = [1083235916354703430, 896342418859892736, 915795164721709077]
|
89 |
+
channel = message.channel
|
90 |
+
author = message.author
|
91 |
+
|
92 |
+
channelId = int(channel.id)
|
93 |
+
authorId = int(author.id)
|
94 |
+
#print(f"Channel id is {channelId}, author id is {authorId}")
|
95 |
+
|
96 |
+
imageList = []
|
97 |
+
for item in message.attachments:
|
98 |
+
if item.content_type.startswith("image"):
|
99 |
+
imageList.append(item.url)
|
100 |
+
|
101 |
+
if message.author.bot or msg.startswith("p!"):
|
102 |
+
pass
|
103 |
+
|
104 |
+
elif (authorId in ADMINS) and (channelId in validMediaChannels) and (message.attachments) and (imageList):
|
105 |
+
print(f'IMAGE: New attachment recieved from {author} in channel {channel}. Processing...')
|
106 |
+
|
107 |
+
await message.add_reaction("📬")
|
108 |
+
jobStatusMessage = await message.reply(f"Status: Upscale queued.", mention_author=False)
|
109 |
+
|
110 |
+
if CurrentlyProcessingJob:
|
111 |
+
pendingTemplate = f"__Status__: Upscale %CURRENTSTEP% for job *{message.id}*"
|
112 |
+
await jobStatusMessage.edit(content=pendingTemplate.replace("%CURRENTSTEP%", "Pending 🕰️"))
|
113 |
+
print(f"IMAGE: There is already a job running. Message id {message.id} has been put on queue")
|
114 |
+
while CurrentlyProcessingJob:
|
115 |
+
await asyncio.sleep(1)
|
116 |
+
#wait untill it finishes
|
117 |
+
await jobStatusMessage.edit(content=pendingTemplate.replace("%CURRENTSTEP%", "Starting 🏃"))
|
118 |
+
print(f"IMAGE: Starting job {message.id}")
|
119 |
+
CurrentlyProcessingJob = True
|
120 |
+
|
121 |
+
attachmentCount = len(imageList)
|
122 |
+
multipleImages = attachmentCount > 1
|
123 |
+
if multipleImages:
|
124 |
+
batchstart = perf_counter()
|
125 |
+
taskType = "Batch Upscale"
|
126 |
+
else:
|
127 |
+
taskType = "Image Upscaling"
|
128 |
+
|
129 |
+
for i in range(attachmentCount):
|
130 |
+
|
131 |
+
"""PREPROCESSING"""
|
132 |
+
fileid = f"{message.id}_{i}"
|
133 |
+
statusTemplate = f"__Status__: %CURRENTSTEP% image from message *{message.id}* **({i+1}/{attachmentCount})**."
|
134 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "🔄 Preprocessing"))
|
135 |
+
|
136 |
+
starttime = perf_counter()
|
137 |
+
url = imageList[i]
|
138 |
+
|
139 |
+
extension = url.split(".")[-1]
|
140 |
+
#fileid = generate_id(f"{imagepath}\input")
|
141 |
+
|
142 |
+
inputfile = os.path.join(inputpath, f"{fileid}.{extension}")
|
143 |
+
outputfile = os.path.join(outputpath, f"{fileid}.{outext}")
|
144 |
+
|
145 |
+
with open (inputfile, "wb") as f:
|
146 |
+
f.write(requests.get(url).content)
|
147 |
+
|
148 |
+
|
149 |
+
|
150 |
+
"""UPSCALE AUTO-CONFIG"""
|
151 |
+
|
152 |
+
if ai_model == "realesr-animevideov3":
|
153 |
+
scale = "2"
|
154 |
+
tilesize = "768"
|
155 |
+
resizeRequired = False
|
156 |
+
else:
|
157 |
+
scale = "4"
|
158 |
+
tilesize = "256"
|
159 |
+
resizeRequired = True
|
160 |
+
|
161 |
+
#Scale 2 only works with the default model
|
162 |
+
execute_upscale = fr"{executablepath} -i {inputfile} -o {outputfile.replace('jpg','png')} -s {scale} -n {ai_model} -f png -t {tilesize} -j 1:1:1"
|
163 |
+
|
164 |
+
"""UPSCALING"""
|
165 |
+
pendtime = perf_counter()
|
166 |
+
preprocessingtime = round((pendtime-starttime), 2)
|
167 |
+
print(f"PREPROCESS: Completed in {preprocessingtime}s.\nUPSCALE:")
|
168 |
+
await jobStatusMessage.edit(content=(statusTemplate.replace("%CURRENTSTEP%", "🧠 Upscaling")+"\nThis may take a while..."))
|
169 |
+
#os.system(execute_upscale)
|
170 |
+
subprocess.run(execute_upscale, shell=True)
|
171 |
+
|
172 |
+
uendtime = perf_counter()
|
173 |
+
|
174 |
+
upscaletime = round((uendtime-pendtime),2)
|
175 |
+
print(f"UPSCALE: Completed in {upscaletime}s.")
|
176 |
+
|
177 |
+
|
178 |
+
|
179 |
+
"""RESIZING"""
|
180 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "⚙️ Resizing"))
|
181 |
+
if resizeRequired:
|
182 |
+
subprocess.run(f"mogrify -format {outext} -resize 50% {outputfile.replace('jpg','png')}", shell=True)
|
183 |
+
else:
|
184 |
+
subprocess.run(f"mogrify -format {outext} {outputfile.replace('jpg','png')}", shell=True)
|
185 |
+
rendtime = perf_counter()
|
186 |
+
resizingtime = round((rendtime-uendtime), 2)
|
187 |
+
print(f"RESIZE: Completed in {resizingtime}s.")
|
188 |
+
|
189 |
+
|
190 |
+
|
191 |
+
"""DELIVERING"""
|
192 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "✉️ Sending"))
|
193 |
+
|
194 |
+
try:
|
195 |
+
|
196 |
+
file=discord.File(outputfile)
|
197 |
+
imgurl = f"attachment://{fileid}.{outext}"
|
198 |
+
prepembed=discord.Embed(title="Sucessfully Upscaled Image")
|
199 |
+
prepembed.set_image(url=imgurl)
|
200 |
+
|
201 |
+
jobResultMessage = await message.channel.send(embed=prepembed, file=file)
|
202 |
+
sendtime = perf_counter()
|
203 |
+
localImageUrl = f"{serverurl}/{fileid}.{outext}"
|
204 |
+
|
205 |
+
|
206 |
+
outputImageUrl = jobResultMessage.embeds[0].image.url
|
207 |
+
#print(outputImageUrl)
|
208 |
+
#outputImageUrl = jobResultMessage.attachments[0].url
|
209 |
+
|
210 |
+
#print(outputImageUrl)
|
211 |
+
embed = discord.Embed(title="Upscaled Image", url=outputImageUrl, description=f"[Local File]({localImageUrl})")
|
212 |
+
embed.set_author(name=author, icon_url=message.author.avatar_url)
|
213 |
+
embed.set_image(url=imgurl)
|
214 |
+
|
215 |
+
sendingtime = round((sendtime-rendtime), 2)
|
216 |
+
processingstats = f"Preprocessing: {preprocessingtime}s | Resizing: {resizingtime}s | Sending: {sendingtime}s."
|
217 |
+
embed.set_footer(text=f"Took {upscaletime}s to upscale {fileid}.\nUpscaling done with {ai_model}.\n{processingstats}")
|
218 |
+
|
219 |
+
await jobResultMessage.edit(embed=embed)
|
220 |
+
|
221 |
+
except discord.errors.HTTPException as e:
|
222 |
+
baseErrorMessage = f"There was an error sending the output for image id {fileid}."
|
223 |
+
if '413 Payload Too Large' in str(e):
|
224 |
+
await message.reply(f"{baseErrorMessage} It was too large for discord to handle.\n```python\n{e}```")
|
225 |
+
else:
|
226 |
+
await message.reply(f"{baseErrorMessage}\n```python\n{e}```")
|
227 |
+
|
228 |
+
except Exception as e:
|
229 |
+
await message.reply(f"Encountered an error while processing attachment {fileid}\n```python\n{e}```")
|
230 |
+
|
231 |
+
|
232 |
+
|
233 |
+
"""CLEANING UP"""
|
234 |
+
#Already finished the whole job.
|
235 |
+
await jobStatusMessage.edit(content=f"\n__Status__: ✅ Job Completed for all {attachmentCount} attachments from message {message.id} at {getloggingtime()}".replace("all 1 attachments", "an attachment"), mention_author=False)
|
236 |
+
await message.delete()
|
237 |
+
await asyncio.sleep(2)
|
238 |
+
await jobStatusMessage.delete()
|
239 |
+
|
240 |
+
|
241 |
+
|
242 |
+
print(f"IMAGE: Job finished for messageID {message.id}\n")
|
243 |
+
CurrentlyProcessingJob = False
|
244 |
+
|
245 |
+
else:
|
246 |
+
print(f"MESSAGE: {channel}- {author}: {msg}")
|
247 |
+
await client.process_commands(message)
|
248 |
+
|
249 |
+
|
250 |
+
"""BOT COMMANDS"""
|
251 |
+
|
252 |
+
@client.command()
|
253 |
+
async def test(ctx):
|
254 |
+
await ctx.send("Hello!")
|
255 |
+
|
256 |
+
@client.command()
|
257 |
+
async def ping(ctx):
|
258 |
+
await ctx.send(f'The ping is {round(client.latency * 1000)}ms.')
|
259 |
+
|
260 |
+
@client.command()
|
261 |
+
async def cleandir(ctx):
|
262 |
+
if ctx.author.id in ADMINS:
|
263 |
+
await ctx.channel.purge(limit=file_cleanup()+1)
|
264 |
+
await ctx.send(f'Successfully images directories', delete_after= 2)
|
265 |
+
else:
|
266 |
+
await ctx.send('You do not have the permissions to perform this action.')
|
267 |
+
|
268 |
+
@client.command()
|
269 |
+
async def clear(ctx, amount=None):
|
270 |
+
if ctx.author.id in ADMINS:
|
271 |
+
if amount == "all":
|
272 |
+
amount = 100
|
273 |
+
else:
|
274 |
+
try:
|
275 |
+
amount = int(amount)
|
276 |
+
except:
|
277 |
+
amount = 2
|
278 |
+
await ctx.channel.purge(limit=amount)
|
279 |
+
await ctx.send(f"Finished clearing {amount} messages {ctx.message.author.mention}", delete_after=4)
|
280 |
+
else:
|
281 |
+
await ctx.send('You do not have the permissions to clear messages using this bot.')
|
282 |
+
|
283 |
+
logmsg = f"\nBOT: Attempting to starting the bot at {getloggingtime()} GMT+7"
|
284 |
+
print(logmsg)
|
285 |
+
client.run(TOKEN)
|
main.py
ADDED
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, datetime, pytz, discord, shutil, requests, subprocess, asyncio, platform, json
|
2 |
+
from discord.ext import commands
|
3 |
+
from time import perf_counter, sleep
|
4 |
+
from threading import Thread
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
load_dotenv()
|
7 |
+
|
8 |
+
CLIENT_SECRET = os.environ['ClientSecret']
|
9 |
+
TOKEN = os.environ['TestingToken']
|
10 |
+
ADMINS = [766145655038410763, 937495666471628831]
|
11 |
+
|
12 |
+
ostype = platform.system()
|
13 |
+
print(f"\nDetected OS: {ostype}")
|
14 |
+
if ostype == "Windows":
|
15 |
+
basepath = r"C:\Users\Pawin\Software\real-esrgan"
|
16 |
+
executablepath = fr"{basepath}\realesrgan-ncnn-vulkan.exe"
|
17 |
+
webserverpath = r"C:\Users\Pawin\Code\local-server\main.py"
|
18 |
+
startWScmd = f"python {webserverpath}"
|
19 |
+
serverurl = "http://thinkpad.pawin.tk/upscale"
|
20 |
+
|
21 |
+
elif ostype == "Linux":
|
22 |
+
basepath = r"./real-esrgan"
|
23 |
+
executablepath = fr"{basepath}/realesrgan-ncnn-vulkan"
|
24 |
+
webserverpath = r"/Code/Flask/main.py"
|
25 |
+
startWScmd = f'python3 {webserverpath}'
|
26 |
+
serverurl = "https://dev.pawin.tk/upscale"
|
27 |
+
|
28 |
+
imagepath = os.path.join(basepath, "discordbot")
|
29 |
+
inputpath = os.path.join(imagepath, "input")
|
30 |
+
outputpath = os.path.join(imagepath, "output")
|
31 |
+
|
32 |
+
|
33 |
+
outext = "jpg" #or png
|
34 |
+
ai_model = "realesr-animevideov3"
|
35 |
+
targetScale = "2" #2 or 4 (string)
|
36 |
+
#ai_model = "realesrgan-x4plus-anime"
|
37 |
+
#Can be realesr-animevideov3(default) | realesrgan-x4plus | realesrgan-x4plus-anime | realesrnet-x4plus
|
38 |
+
|
39 |
+
|
40 |
+
def getloggingtime():
|
41 |
+
return datetime.datetime.now(pytz.timezone('Asia/Bangkok')).strftime('%d/%m/%Y %H:%M:%S')
|
42 |
+
|
43 |
+
def generate_id(path):
|
44 |
+
return str(len(os.listdir(path))+1).zfill(3)
|
45 |
+
|
46 |
+
def file_cleanup():
|
47 |
+
dirlist = [inputpath, outputpath]
|
48 |
+
contentlength = len(os.listdir(inputpath))
|
49 |
+
for directory in dirlist:
|
50 |
+
shutil.rmtree(directory)
|
51 |
+
os.mkdir(directory)
|
52 |
+
print("BOT: Sucessfully cleaned directories")
|
53 |
+
return contentlength
|
54 |
+
|
55 |
+
def start_webserver():
|
56 |
+
if webserverStartEnabled:
|
57 |
+
try:
|
58 |
+
print("Calling Webserver...")
|
59 |
+
subprocess.run(startWScmd, shell=True)
|
60 |
+
except:
|
61 |
+
print("Something went wrong with the webserver.")
|
62 |
+
print("Webserver process launched.")
|
63 |
+
else:
|
64 |
+
print("Webserver Start Skipped.")
|
65 |
+
|
66 |
+
def loadMediaChannels():
|
67 |
+
global mediaChannels
|
68 |
+
with open("resources/mediaChannels.json", "r") as f:
|
69 |
+
mediaChannels = json.load(f)
|
70 |
+
#print("Successfully loaded media channels.")
|
71 |
+
|
72 |
+
def backupMediaChannels():
|
73 |
+
with open("resources/mediaChannels.json", "w") as f:
|
74 |
+
json.dump(mediaChannels, f, indent=2, ensure_ascii=False)
|
75 |
+
#print("Successfully backed up media channel.")
|
76 |
+
|
77 |
+
def toggleMediaChannel(channelId: int):
|
78 |
+
global mediaChannels
|
79 |
+
loadMediaChannels()
|
80 |
+
if channelId in mediaChannels:
|
81 |
+
mediaChannels.remove(channelId)
|
82 |
+
actionDone = "Unmarked"
|
83 |
+
else:
|
84 |
+
mediaChannels.append(int(channelId))
|
85 |
+
actionDone = "Marked"
|
86 |
+
|
87 |
+
backupMediaChannels()
|
88 |
+
resultMsg = f"Successfully {actionDone} this channel as a media channel."
|
89 |
+
print(resultMsg)
|
90 |
+
return resultMsg
|
91 |
+
|
92 |
+
|
93 |
+
print(f"Starting the upscaler bot...")
|
94 |
+
#webserverStartEnabled = input("Do you want to start the webserver also? (y/N): ").lower() == "y"
|
95 |
+
webserverStartEnabled = False
|
96 |
+
#print(f"BTW, automatic directory cleaning have been disabled. To clean directories, please run u!cleandir")
|
97 |
+
|
98 |
+
loadMediaChannels()
|
99 |
+
|
100 |
+
client = commands.Bot(
|
101 |
+
intents = discord.Intents.all(),
|
102 |
+
command_prefix = "u!",
|
103 |
+
case_insensitive = True,
|
104 |
+
help_command = None,
|
105 |
+
activity = discord.Streaming(name = f"u!help | Currently Running Upscaler Bot ", url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
106 |
+
)
|
107 |
+
|
108 |
+
"""BOT EVENTS"""
|
109 |
+
#Show Status When Bot Is Ready
|
110 |
+
@client.event
|
111 |
+
async def on_ready():
|
112 |
+
print('\nBOT: We have successfully logged in as {0.user}'.format(client))
|
113 |
+
file_cleanup()
|
114 |
+
Thread(target=start_webserver, daemon=True).start()
|
115 |
+
|
116 |
+
CurrentlyProcessingJob = False
|
117 |
+
|
118 |
+
@client.event
|
119 |
+
async def on_message(message):
|
120 |
+
global CurrentlyProcessingJob
|
121 |
+
|
122 |
+
msg = str(message.content)
|
123 |
+
validMediaChannels = mediaChannels
|
124 |
+
channel = message.channel
|
125 |
+
author = message.author
|
126 |
+
|
127 |
+
channelId = int(channel.id)
|
128 |
+
authorId = int(author.id)
|
129 |
+
#print(f"Channel id is {channelId}, author id is {authorId}")
|
130 |
+
|
131 |
+
imageList = []
|
132 |
+
for item in message.attachments:
|
133 |
+
if item.content_type.startswith("image"):
|
134 |
+
imageList.append(item.url)
|
135 |
+
|
136 |
+
if message.author.bot or msg.startswith("p!"):
|
137 |
+
pass
|
138 |
+
|
139 |
+
#elif (authorId in ADMINS) and (channelId in validMediaChannels) and (message.attachments) and (imageList):
|
140 |
+
elif (authorId) and (channelId in validMediaChannels) and (message.attachments) and (imageList):
|
141 |
+
|
142 |
+
print(f'IMAGE: New attachment recieved from {author} in channel {channel}. Processing...')
|
143 |
+
|
144 |
+
await message.add_reaction("📬")
|
145 |
+
jobStatusMessage = await message.reply(f"Status: Upscale queued.", mention_author=False)
|
146 |
+
|
147 |
+
if CurrentlyProcessingJob:
|
148 |
+
pendingTemplate = f"__Status__: Upscale %CURRENTSTEP% for job *{message.id}*"
|
149 |
+
await jobStatusMessage.edit(content=pendingTemplate.replace("%CURRENTSTEP%", "Pending 🕰️"))
|
150 |
+
print(f"IMAGE: There is already a job running. Message id {message.id} has been put on queue")
|
151 |
+
while CurrentlyProcessingJob:
|
152 |
+
await asyncio.sleep(1)
|
153 |
+
#wait untill it finishes
|
154 |
+
await jobStatusMessage.edit(content=pendingTemplate.replace("%CURRENTSTEP%", "Starting 🏃"))
|
155 |
+
print(f"IMAGE: Starting job {message.id}")
|
156 |
+
CurrentlyProcessingJob = True
|
157 |
+
|
158 |
+
attachmentCount = len(imageList)
|
159 |
+
multipleImages = attachmentCount > 1
|
160 |
+
if multipleImages:
|
161 |
+
batchstart = perf_counter()
|
162 |
+
taskType = "Batch Upscale"
|
163 |
+
else:
|
164 |
+
taskType = "Image Upscaling"
|
165 |
+
|
166 |
+
for i in range(attachmentCount):
|
167 |
+
|
168 |
+
"""PREPROCESSING"""
|
169 |
+
fileid = f"{message.id}_{i}"
|
170 |
+
statusTemplate = f"__Status__: %CURRENTSTEP% image from message *{message.id}* **({i+1}/{attachmentCount})**."
|
171 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "🔄 Preprocessing"))
|
172 |
+
|
173 |
+
starttime = perf_counter()
|
174 |
+
url = imageList[i]
|
175 |
+
|
176 |
+
extension = url.split(".")[-1]
|
177 |
+
#fileid = generate_id(f"{imagepath}\input")
|
178 |
+
|
179 |
+
inputfile = os.path.join(inputpath, f"{fileid}.{extension}").split("?")[0]
|
180 |
+
outputpng = os.path.join(outputpath, f"{fileid}.png")
|
181 |
+
outputfile = os.path.join(outputpath, f"{fileid}.{outext}")
|
182 |
+
|
183 |
+
with open (inputfile, "wb") as f:
|
184 |
+
f.write(requests.get(url).content)
|
185 |
+
|
186 |
+
|
187 |
+
"""UPSCALE AUTO-CONFIG"""
|
188 |
+
|
189 |
+
if ai_model == "realesr-animevideov3":
|
190 |
+
scale = targetScale
|
191 |
+
tilesize = "768"
|
192 |
+
resizeRequired = False
|
193 |
+
else:
|
194 |
+
scale = "4"
|
195 |
+
tilesize = "256"
|
196 |
+
resizeRequired = True if targetScale == "2" else False
|
197 |
+
|
198 |
+
#Scale 2 only works with the default model
|
199 |
+
execute_upscale = fr"{executablepath} -i {inputfile} -o {outputpng} -n {ai_model} -s {scale} -f png -t {tilesize}"
|
200 |
+
print(execute_upscale)
|
201 |
+
"""UPSCALING"""
|
202 |
+
pendtime = perf_counter()
|
203 |
+
preprocessingtime = round((pendtime-starttime), 2)
|
204 |
+
print(f"PREPROCESS: Completed in {preprocessingtime}s.\nUPSCALE:")
|
205 |
+
await jobStatusMessage.edit(content=(statusTemplate.replace("%CURRENTSTEP%", "🧠 Upscaling")+"\nThis may take a while..."))
|
206 |
+
#os.system(execute_upscale)
|
207 |
+
#subprocess.run(execute_upscale, shell=True)
|
208 |
+
upscaleProcess = await asyncio.create_subprocess_shell(execute_upscale)
|
209 |
+
await upscaleProcess.wait()
|
210 |
+
|
211 |
+
uendtime = perf_counter()
|
212 |
+
|
213 |
+
upscaletime = round((uendtime-pendtime),2)
|
214 |
+
print(f"UPSCALE: Completed in {upscaletime}s.")
|
215 |
+
|
216 |
+
|
217 |
+
|
218 |
+
"""RESIZING"""
|
219 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "⚙️ Resizing"))
|
220 |
+
|
221 |
+
#qualityArgs = f" -quality 95 " if outext == "jpg" else ""
|
222 |
+
qualityArgs = ""
|
223 |
+
|
224 |
+
resizeCommand = f"mogrify -format {outext}{qualityArgs} -resize 50% {outputpng}"
|
225 |
+
convertCommand = f"mogrify -format {outext}{qualityArgs} {outputpng}"
|
226 |
+
|
227 |
+
resizeProcess = await asyncio.create_subprocess_shell(resizeCommand if resizeRequired else convertCommand)
|
228 |
+
await resizeProcess.wait()
|
229 |
+
|
230 |
+
rendtime = perf_counter()
|
231 |
+
resizingtime = round((rendtime-uendtime), 2)
|
232 |
+
print(f"RESIZE: Completed in {resizingtime}s.")
|
233 |
+
|
234 |
+
|
235 |
+
|
236 |
+
"""DELIVERING"""
|
237 |
+
await jobStatusMessage.edit(content=statusTemplate.replace("%CURRENTSTEP%", "✉️ Sending"))
|
238 |
+
|
239 |
+
try:
|
240 |
+
|
241 |
+
file=discord.File(outputfile)
|
242 |
+
imgurl = f"attachment://{fileid}.{outext}"
|
243 |
+
prepembed=discord.Embed(title="Sucessfully Upscaled Image")
|
244 |
+
prepembed.set_image(url=imgurl)
|
245 |
+
|
246 |
+
jobResultMessage = await message.channel.send(embed=prepembed, file=file)
|
247 |
+
sendtime = perf_counter()
|
248 |
+
localImageUrl = f"{serverurl}/{fileid}.{outext}"
|
249 |
+
|
250 |
+
|
251 |
+
outputImageUrl = jobResultMessage.embeds[0].image.url
|
252 |
+
#print(outputImageUrl)
|
253 |
+
#outputImageUrl = jobResultMessage.attachments[0].url
|
254 |
+
|
255 |
+
#print(outputImageUrl)
|
256 |
+
embed = discord.Embed(title="Upscaled Image", url=outputImageUrl, description=f"[Local File]({localImageUrl})")
|
257 |
+
embed.set_author(name=author, icon_url=message.author.avatar)
|
258 |
+
embed.set_image(url=imgurl)
|
259 |
+
|
260 |
+
sendingtime = round((sendtime-rendtime), 2)
|
261 |
+
processingstats = f"Preprocessing: {preprocessingtime}s | Resizing: {resizingtime}s | Sending: {sendingtime}s."
|
262 |
+
embed.set_footer(text=f"Took {upscaletime}s to upscale {fileid}.\nUpscaling done with {ai_model}.\n{processingstats}")
|
263 |
+
|
264 |
+
await jobResultMessage.edit(embed=embed)
|
265 |
+
|
266 |
+
except discord.errors.HTTPException as e:
|
267 |
+
baseErrorMessage = f"There was an error sending the output for image id {fileid}."
|
268 |
+
if '413 Payload Too Large' in str(e):
|
269 |
+
await message.reply(f"{baseErrorMessage} It was too large for discord to handle.\n```python\n{e}```")
|
270 |
+
else:
|
271 |
+
await message.reply(f"{baseErrorMessage}\n```python\n{e}```")
|
272 |
+
|
273 |
+
except Exception as e:
|
274 |
+
await message.reply(f"Encountered an error while processing attachment {fileid}\n```python\n{e}```")
|
275 |
+
|
276 |
+
|
277 |
+
|
278 |
+
"""CLEANING UP"""
|
279 |
+
#Already finished the whole job.
|
280 |
+
await jobStatusMessage.edit(content=f"\n__Status__: ✅ Job Completed for all {attachmentCount} attachments from message {message.id} at {getloggingtime()}".replace("all 1 attachments", "an attachment"))
|
281 |
+
#await message.delete()
|
282 |
+
await asyncio.sleep(2)
|
283 |
+
await jobStatusMessage.delete()
|
284 |
+
|
285 |
+
|
286 |
+
|
287 |
+
print(f"IMAGE: Job finished for messageID {message.id}\n")
|
288 |
+
CurrentlyProcessingJob = False
|
289 |
+
|
290 |
+
else:
|
291 |
+
print(f"MESSAGE: {channel}- {author}: {msg}")
|
292 |
+
await client.process_commands(message)
|
293 |
+
|
294 |
+
|
295 |
+
"""BOT COMMANDS"""
|
296 |
+
|
297 |
+
@client.command()
|
298 |
+
async def test(ctx):
|
299 |
+
await ctx.send("Hello!")
|
300 |
+
|
301 |
+
@client.command()
|
302 |
+
async def ping(ctx):
|
303 |
+
await ctx.send(f'The ping is {round(client.latency * 1000)}ms.')
|
304 |
+
|
305 |
+
@client.command()
|
306 |
+
async def cleandir(ctx):
|
307 |
+
if ctx.author.id in ADMINS:
|
308 |
+
await ctx.channel.purge(limit=file_cleanup()+1)
|
309 |
+
await ctx.send(f'Successfully images directories', delete_after= 2)
|
310 |
+
else:
|
311 |
+
await ctx.send('You do not have the permissions to perform this action.')
|
312 |
+
|
313 |
+
@client.command()
|
314 |
+
async def clear(ctx, amount=None):
|
315 |
+
if ctx.author.id in ADMINS:
|
316 |
+
if amount == "all":
|
317 |
+
amount = 100
|
318 |
+
else:
|
319 |
+
try:
|
320 |
+
amount = int(amount)
|
321 |
+
except:
|
322 |
+
amount = 2
|
323 |
+
await ctx.channel.purge(limit=amount)
|
324 |
+
await ctx.send(f"Finished clearing {amount} messages {ctx.message.author.mention}", delete_after=4)
|
325 |
+
else:
|
326 |
+
await ctx.send('You do not have the permissions to clear messages using this bot.')
|
327 |
+
|
328 |
+
@client.command(aliases = ['enable-upscale'])
|
329 |
+
async def toggleUpscale(ctx):
|
330 |
+
result = toggleMediaChannel(int(ctx.channel.id))
|
331 |
+
#print(result)
|
332 |
+
await ctx.send(result)
|
333 |
+
|
334 |
+
logmsg = f"\nBOT: Attempting to starting the bot at {getloggingtime()} GMT+7"
|
335 |
+
print(logmsg)
|
336 |
+
client.run(TOKEN)
|
readme.md
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Upscaler bot
|
2 |
+
|
3 |
+
## A discord bot to upscale images!
|
4 |
+
|
5 |
+
## Features:
|
6 |
+
- Easy Upscaling: simply paste the image in the specified channel, and it'll be done in a minute!
|
7 |
+
- Local mode: option to receive image instantly via the local network (requires the Flask repository).
|
8 |
+
- Multiple modes: choose whether optimize for speed, general photos, or anime/graphics!
|
9 |
+
|
10 |
+
## Requirements:
|
11 |
+
- discord, pytz, python-dotenv, requests
|
12 |
+
- Use `pip install -r requirements.txt`
|
13 |
+
- [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN/releases/tag/v0.2.5.0) vulkan executable.
|
14 |
+
- Download the zip file from the link above and extract to the real-esrgan folder.
|
15 |
+
- My [Flask server](https://github.com/PawinChan/Flask).
|
16 |
+
- Clone to ~/Code/Flask and run.
|
real-esrgan/Upscaling Notes.txt
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Sample Command:
|
2 |
+
|
3 |
+
./realesrgan-ncnn-vulkan -i input -o output -s 2 -t 512 -n realesrgan-x4plus-anime -g 0 -j 1:1:1 -f png -v
|
4 |
+
===============================================================================
|
5 |
+
|
6 |
+
Usage: realesrgan-ncnn-vulkan -i infile -o outfile [options]...
|
7 |
+
|
8 |
+
-h show this help
|
9 |
+
-i input-path input image path (jpg/png/webp) or directory
|
10 |
+
-s scale upscale ratio (can be 2, 3, 4. default=4)
|
11 |
+
-t tile-size tile size (>=32/0=auto, default=0) can be 0,0,0 for multi-gpu
|
12 |
+
-m model-path folder path to the pre-trained models. default=models
|
13 |
+
-n model-name model name (default=realesr-animevideov3, can be realesr-animevideov3 | realesrgan-x4plus | realesrgan-x4plus-anime | realesrnet-x4plus)
|
14 |
+
-g gpu-id gpu device to use (default=auto) can be 0,1,2 for multi-gpu
|
15 |
+
-x enable tta mode
|
16 |
+
-f format output image format (jpg/png/webp, default=ext/png)
|
17 |
+
-v verbose output
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
discord
|
2 |
+
requests
|
3 |
+
python-dotenv
|
4 |
+
pytz
|
resources/mediaChannels.json
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
1083235916354703430,
|
3 |
+
896342418859892736,
|
4 |
+
915795164721709077,
|
5 |
+
1086939553341911141,
|
6 |
+
1119562864995344394
|
7 |
+
]
|
upscaler-bot.code-workspace
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"folders": [
|
3 |
+
{
|
4 |
+
"path": "."
|
5 |
+
},
|
6 |
+
{
|
7 |
+
"path": "../../Software/real-esrgan/discordbot"
|
8 |
+
}
|
9 |
+
],
|
10 |
+
"settings": {}
|
11 |
+
}
|
upscaler-docs.md
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
## Help Command
|
3 |
+
```
|
4 |
+
PS C:\Users\Pawin\Software\real-esrgan> ./realesrgan-ncnn-vulkan.exe
|
5 |
+
Usage: realesrgan-ncnn-vulkan -i infile -o outfile [options]...
|
6 |
+
|
7 |
+
-h show this help
|
8 |
+
-i input-path input image path (jpg/png/webp) or directory
|
9 |
+
-o output-path output image path (jpg/png/webp) or directory
|
10 |
+
-s scale upscale ratio (can be 2, 3, 4. default=4)
|
11 |
+
-t tile-size tile size (>=32/0=auto, default=0) can be 0,0,0 for multi-gpu
|
12 |
+
-m model-path folder path to the pre-trained models. default=models
|
13 |
+
-n model-name model name (default=realesr-animevideov3, can be realesr-animevideov3 | realesrgan-x4plus | realesrgan-x4plus-anime | realesrnet-x4plus)
|
14 |
+
-g gpu-id gpu device to use (default=auto) can be 0,1,2 for multi-gpu
|
15 |
+
-j load:proc:save thread count for load/proc/save (default=1:2:2) can be 1:2,2,2:2 for multi-gpu
|
16 |
+
-x enable tta mode
|
17 |
+
-f format output image format (jpg/png/webp, default=ext/png)
|
18 |
+
-v verbose output
|
19 |
+
```
|
20 |
+
|
21 |
+
## STDOUT Output
|
22 |
+
```
|
23 |
+
[0 Intel(R) HD Graphics 520] queueC=0[1] queueG=0[1] queueT=0[1]
|
24 |
+
[0 Intel(R) HD Graphics 520] bugsbn1=0 bugbilz=209 bugcopc=0 bugihfa=0
|
25 |
+
[0 Intel(R) HD Graphics 520] fp16-p/s/a=1/1/1 int8-p/s/a=1/1/1
|
26 |
+
[0 Intel(R) HD Graphics 520] subgroup=32 basic=1 vote=1 ballot=1 shuffle=1
|
27 |
+
0.00%
|
28 |
+
input.png -> output.png done
|
29 |
+
```
|