all repos — python-meme-bot @ 7f2b1a503ee21ef09535d7ecd14fa5ca6d29b9a3

Telegram Bot that uses PIL to compute light image processing.

minor bug fixes
Bi-Rabittoh andronacomarco@gmail.com
Thu, 08 Sep 2022 20:17:23 +0200
commit

7f2b1a503ee21ef09535d7ecd14fa5ca6d29b9a3

parent

23056f54bf43bea42b9437056f1152e31f526381

4 files changed, 129 insertions(+), 70 deletions(-)

jump to
M Api.pyApi.py

@@ -53,8 +53,6 @@ params['page'] = random.randint(1, max_pages)

page = requests.get(base_url + page_suffix, params).json() n = random.randint(0, params['limit'] - 1) - #print("Page: " + str(params['page'])) - #print("File: " + str(n)) try: file_url = page[n]['file_url'] if not _valid_extension(file_url):
M Constants.pyConstants.py

@@ -1,3 +1,5 @@

+import logging + localization = { 'us': { 'welcome' : "Welcome to PILuAnimeBot!",

@@ -8,6 +10,7 @@ 'enabled' : "enabled",

'disabled' : "disabled", 'unknown' : "Sorry, I didn't understand that command.", 'error': "An error has occurred. Please retry.", + 'failed_effect': "Couldn't apply effect." }, 'it': { 'welcome' : "Benvenuto da PILuAnimeBot!",

@@ -18,6 +21,7 @@ 'enabled' : "abilitata",

'disabled' : "disabilitata", 'unknown' : "Non ho capito.", 'error': "Qualcosa è andato storto, riprova.", + 'failed_effect': "Impossibile applicare l'effetto." }, }
M Effects.pyEffects.py

@@ -1,4 +1,5 @@

from PIL import Image, ImageDraw, ImageFont, ImageEnhance +from io import BytesIO import textwrap, os, random, time, math random.seed(time.time())

@@ -7,19 +8,35 @@ BASE_WIDTH = 1200

IMPACT_FONT_FILE = os.path.join("fonts", "impact.ttf") ARIAL_FONT_FILE = os.path.join("fonts", "opensans.ttf") +k1 = 0.612123 +k2 = 1.216428 +k3 = 0.341428 +k4 = 0.364576 +''' +k1 = 0.6 +k2 = 2 * k1 +k3 = k1 / 2 +k4 = k3 +''' def _get_font_size(h, w, n, letter_spacing, line_spacing): - - k1 = 0.612123 - k2 = 1.216428 - k3 = 0.341428 - k4 = 0.364576 - + font_size = (math.sqrt(4 * k1 * k2 * h * n * w + math.pow(k2, 2) * math.pow(n, 2) * math.pow(letter_spacing, 2) + ((2 * k1 * k2 * k3 - 2 * k4 * math.pow(k2, 2)) * math.pow(n, 2) - 2 * k1 * k2 * line_spacing) * letter_spacing + math.pow(k1, 2) * math.pow(n, 2) * math.pow(line_spacing, 2) + (2 * k1 * k4 * k2 - 2 * math.pow(k1, 2) * k3) * math.pow(n, 2) * line_spacing + (math.pow(k1, 2) * math.pow(k3, 2) - 2 * k1 * k4 * k2 * k3 + math.pow(k4, 2) * math.pow(k2, 2)) * math.pow(n, 2)) - k2 * n * letter_spacing - k1 * n * line_spacing + (k1 * k3 + k4 * k2) * n) / (2 * k1 * k2 * n) line_width = w / (k1 * font_size - k4 + letter_spacing) return font_size, line_width +def img_to_bio(image): + + bio = BytesIO() + + if image.mode in ("RGBA", "P"): + image = image.convert("RGB") + + image.save(bio, 'JPEG') + bio.seek(0) + return bio + def _darken_image(image: Image, amount=0.5): return ImageEnhance.Brightness(image).enhance(amount)

@@ -29,7 +46,8 @@ for i in range(len(line)):

d.text((x, y), line[i], fill=fill, stroke_width=stroke_width, font=font, stroke_fill=stroke_fill) x += font.getlength(line[i]) + letter_spacing -def tt_bt_effect(text: str, img: Image): +def _draw_tt_bt(text: str, img: Image, bottom=False): + LETTER_SPACING = 9 LINE_SPACING = 10 FILL = (255, 255, 255)

@@ -37,34 +55,54 @@ STROKE_WIDTH = 9

STROKE_FILL = (0, 0, 0) FONT_BASE = 100 MARGIN = 10 - - def _draw_tt_bt(text, img, bottom=False): - split_caption = textwrap.wrap(text.upper(), width=20) - if split_caption == []: - return - font_size = FONT_BASE + 10 if len(split_caption) <= 1 else FONT_BASE - font = ImageFont.truetype(font=IMPACT_FONT_FILE, size=font_size) - img_width, img_height = img.size - d = ImageDraw.Draw(img) - txt_height = d.textbbox((0, 0), split_caption[0], font=font)[3] + split_caption = textwrap.wrap(text.upper(), width=20) + if split_caption == []: + return + font_size = FONT_BASE + 10 if len(split_caption) <= 1 else FONT_BASE + font = ImageFont.truetype(font=IMPACT_FONT_FILE, size=font_size) + img_width, img_height = img.size - if bottom: - factor = -1 - split_caption.reverse() - y = (img_height - (img_height / MARGIN)) - (txt_height / 2) - else: - factor = 1 - y = (img_height / MARGIN) - (txt_height / 1.5) + d = ImageDraw.Draw(img) + txt_height = d.textbbox((0, 0), split_caption[0], font=font)[3] - for line in split_caption: - txt_width = d.textbbox((0, 0), line, font=font)[2] + if bottom: + factor = -1 + split_caption.reverse() + y = (img_height - (img_height / MARGIN)) - (txt_height / 2) + else: + factor = 1 + y = (img_height / MARGIN) - (txt_height / 1.5) - x = (img_width - txt_width - (len(line) * LETTER_SPACING)) / 2 + for line in split_caption: + txt_width = d.textbbox((0, 0), line, font=font)[2] - _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL) + x = (img_width - txt_width - (len(line) * LETTER_SPACING)) / 2 + + _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL) + + y += (txt_height + LINE_SPACING) * factor + +def bt_effect(text: str, img: Image): - y += (txt_height + LINE_SPACING) * factor + lines = [x for x in text.split("\n") if x] + bt = lines[0] if len(lines) > 0 else None + + img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0])))) + + if bt is None: + return img + + _draw_tt_bt(bt, img, bottom=True) + + img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0]))) + + if img.mode in ("RGBA", "P"): + img = img.convert("RGB") + + return img_to_bio(img) + +def tt_bt_effect(text: str, img: Image): lines = [x for x in text.split("\n") if x]

@@ -86,7 +124,7 @@

if img.mode in ("RGBA", "P"): img = img.convert("RGB") - return img + return img_to_bio(img) def splash_effect(input_text: str, img: Image): LETTER_SPACING = 1

@@ -107,7 +145,7 @@ n = len(input_text.strip())

FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING) - lines = [x for x in input_text.split("\n") if x] + lines = [ x for x in input_text.split("\n") if x ] first_line = lines.pop(0) text = "\n".join(lines)

@@ -152,7 +190,7 @@

if img.mode in ("RGBA", "P"): img = img.convert("RGB") - return img + return img_to_bio(img) def wot_effect(input_text: str, img: Image): LETTER_SPACING = 1

@@ -166,19 +204,23 @@ img = _darken_image(img)

img_width, img_height = img.size - MARGIN_H = img_height / 4 + n = len(input_text.strip()) + + MARGIN_H = img_height / (n / 270) MARGIN_W = 0 w = img_width - MARGIN_W h = img_height - MARGIN_H - n = len(input_text.strip()) - FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING) + try: + FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING) + except ValueError: + return None text = textwrap.wrap(input_text.strip(), width=int(LINE_WIDTH)) if text == []: - return + return None font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE)) d = ImageDraw.Draw(img)

@@ -200,7 +242,7 @@

if img.mode in ("RGBA", "P"): img = img.convert("RGB") - return img + return img_to_bio(img) def text_effect(text: str, img: Image): LETTER_SPACING = 1

@@ -253,31 +295,33 @@

if img.mode in ("RGBA", "P"): img = img.convert("RGB") - return img + return img_to_bio(img) def test_multiple(text, effect, modifier=""): - + imgs = os.listdir("test") for i in range(len(imgs)): image = effect(text, Image.open(os.path.join("test", imgs[i]))) - image.save(os.path.join("test_output", f'output{modifier}{i}.jpg'), optimize=True, quality=80) + Image.open(image).save(os.path.join("test_output", f'output{modifier}{i}.jpg'), optimize=True, quality=80) print("Image test successful") def test(text, effect, modifier=""): - + image = effect(text, Image.open("image.jpg")) - image.save('output.jpg', optimize=True, quality=80) + Image.open(image).save('output.jpg', optimize=True, quality=80) print("Image test successful") def main(): - input_text = '''Bi-Rabittoh -Prova wow + input_text = ''' +Il giornalista italiano Mattia Sorbi è stato ferito nella zona occupata di Kherson. Non aveva con sé un interprete, l'autista che lo accompagnava sarebbe morto. + +Godo ''' #test(input_text, splash_effect) - test_multiple(input_text, splash_effect) + test_multiple(input_text, tt_bt_effect) #test_multiple(input_text, splash_effect, "_long") if __name__ == "__main__":
M main.pymain.py

@@ -1,6 +1,6 @@

from PIL import Image from Api import get_random_image, rating_normal, rating_lewd -from Effects import tt_bt_effect, splash_effect, wot_effect, text_effect +from Effects import img_to_bio, tt_bt_effect, bt_effect, splash_effect, wot_effect, text_effect from Constants import get_localized_string as l from dotenv import load_dotenv

@@ -19,17 +19,6 @@ def _get_args(context):

logging.info(context.args) return ' '.join(context.args) - -def _img_to_bio(image): - - bio = BytesIO() - - if image.mode in ("RGBA", "P"): - image = image.convert("RGB") - - image.save(bio, 'JPEG') - bio.seek(0) - return bio def _get_message_content(message):

@@ -89,7 +78,7 @@

markup = InlineKeyboardMarkup([[InlineKeyboardButton(text=l("sauce", lang), url=url)]]) if bio: - return _img_to_bio(image), markup + return image, markup return image, markup def _get_all(update, check_fn, context):

@@ -110,7 +99,7 @@ "image": image_content

} } - logging.info(f"User {update.message.from_user.username} typed: {str(update.message.text)}") + logging.info(f"User {update.message.from_user.full_name} (@{update.message.from_user.username}) typed: {str(update.message.text)}") content = check_fn(info_struct)

@@ -154,7 +143,7 @@ except IndexError:

tag = "" image, markup = _get_image(context, tag) - update.message.reply_photo(photo=image, parse_mode="markdown", reply_markup=markup) + update.message.reply_photo(photo=img_to_bio(image), parse_mode="markdown", reply_markup=markup) def raw(update: Update, context: CallbackContext):

@@ -277,7 +266,11 @@ if image is None:

update.message.reply_text(l("no_caption", lang)) return - image = _img_to_bio(tt_bt_effect(content, image)) + image = tt_bt_effect(content, image) + + if image is None: + update.message.reply_text(l("failed_effect", lang)) + update.message.reply_photo(photo=image, reply_markup=markup)

@@ -289,7 +282,11 @@ if image is None:

update.message.reply_text(l("no_caption", lang)) return - image = _img_to_bio(tt_bt_effect(content, image)) + image = tt_bt_effect(content, image) + + if image is None: + update.message.reply_text(l("failed_effect", lang)) + update.message.reply_photo(photo=image, reply_markup=markup) def bt(update: Update, context: CallbackContext):

@@ -299,8 +296,12 @@

if image is None: update.message.reply_text(l("no_caption", lang)) return - - image = _img_to_bio(tt_bt_effect("‎\n" + content, image)) + + image = bt_effect(content, image) + + if image is None: + update.message.reply_text(l("failed_effect", lang)) + update.message.reply_photo(photo=image, reply_markup=markup) def splash(update: Update, context: CallbackContext):

@@ -311,7 +312,11 @@ if image is None:

update.message.reply_text(l("no_caption", lang)) return - image = _img_to_bio(splash_effect(content, image)) + image = splash_effect(content, image) + + if image is None: + update.message.reply_text(l("failed_effect", lang)) + update.message.reply_photo(photo=image, reply_markup=markup) def wot(update: Update, context: CallbackContext):

@@ -322,7 +327,11 @@ if image is None:

update.message.reply_text(l("no_caption", lang)) return - image = _img_to_bio(wot_effect(content, image)) + image = wot_effect(content, image) + + if image is None: + update.message.reply_text(l("failed_effect", lang)) + update.message.reply_photo(photo=image, reply_markup=markup) def text(update: Update, context: CallbackContext):

@@ -333,7 +342,11 @@ if image is None:

update.message.reply_text(l("no_caption", lang)) return - image = _img_to_bio(text_effect(content, image)) + image = text_effect(content, image) + + if image is None: + update.message.reply_text(l("failed_effect", lang)) + update.message.reply_photo(photo=image, reply_markup=markup) def caps(update: Update, context: CallbackContext):

@@ -342,7 +355,7 @@ _, reply, _ = _get_reply(update.message.reply_to_message, _get_args(context))

context.bot.send_message(chat_id=update.effective_chat.id, text=reply.upper()) def unknown(update: Update, context: CallbackContext): - logging.info(f"User {update.message.from_user.username} sent {update.message.text_markdown_v2} and I don't know what that means.") + logging.info(f"User {update.message.from_user.full_name} sent {update.message.text_markdown_v2} and I don't know what that means.") def error_callback(update: Update, context: CallbackContext): try: