all repos — python-meme-bot @ 527645384b084fc8e88633dd3057b8a05140a4d6

Telegram Bot that uses PIL to compute light image processing.

add /splash command
Marco Andronaco andronacomarco@gmail.com
Tue, 06 Sep 2022 18:41:26 +0200
commit

527645384b084fc8e88633dd3057b8a05140a4d6

parent

735d49cd8cabf16eae27053532192db24cee5cbb

6 files changed, 213 insertions(+), 43 deletions(-)

jump to
M Api.pyApi.py

@@ -6,9 +6,6 @@

base_url = "https://danbooru.donmai.us/" base_url_test = "https://testbooru.donmai.us/" -page_suffix = "post/index.json" -post_suffix = "posts/" - ratings = [ 'g', # general 's', # sensitive

@@ -16,10 +13,8 @@ 'q', # questionable

'e', # explicit ] -rating_normal = "rating:g,s" -rating_lewd = "rating:q" - - +rating_normal = "rating:g" +rating_lewd = "rating:s,q" supported_file_types = [ ".jpg",

@@ -28,12 +23,15 @@ ".png"

] def _valid_extension(fname: str): - for t in supported_file_types: + for t in [".jpg", ".jpeg", ".png"]: if fname.lower().endswith(t): return True return False def get_random_image(rating=rating_normal, tags=""): + page_suffix = "post/index.json" + post_suffix = "posts/" + limit = 100 max_pages = 1000 sleep_seconds = 3
M Effects.pyEffects.py

@@ -1,13 +1,20 @@

-from PIL import Image, ImageDraw, ImageFont -import textwrap, os +from PIL import Image, ImageDraw, ImageFont, ImageEnhance +import textwrap, os +BASE_WIDTH = 1200 +IMPACT_FONT_FILE = os.path.join("fonts", "impact.ttf") +ARIAL_FONT_FILE = os.path.join("fonts", "opensans.ttf") - +def _darken_image(image: Image, amount=0.5): + return ImageEnhance.Brightness(image).enhance(amount) +def _draw_line(d: ImageDraw, x: int, y: int, line: str, font: ImageFont, letter_spacing: int = 9, fill = (255, 255, 255), stroke_width: int = 9, stroke_fill = (0, 0, 0)): + + 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, img): - IMPACT_FONT_FILE = os.path.join("fonts", "impact.ttf") - BASE_WIDTH = 1200 +def tt_bt_effect(text: str, img: Image): LETTER_SPACING = 9 LINE_SPACING = 10 FILL = (255, 255, 255)

@@ -33,20 +40,16 @@ split_caption.reverse()

y = (img_height - (img_height / MARGIN)) - (txt_height / 2) else: factor = 1 - y = ((img_height / MARGIN)) - (txt_height / 1.5) + y = (img_height / MARGIN) - (txt_height / 1.5) for line in split_caption: txt_width = d.textbbox((0, 0), line, font=font)[2] x = (img_width - txt_width - (len(line) * LETTER_SPACING))/2 - for i in range(len(line)): - char = line[i] - width = font.getlength(char) - d.text((x, y), char, fill=FILL, stroke_width=STROKE_WIDTH, font=font, stroke_fill=STROKE_FILL) - x += width + LETTER_SPACING + _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL) - y = y + (txt_height + LINE_SPACING) * factor + y += (txt_height + LINE_SPACING) * factor lines = [x for x in text.split("\n") if x]

@@ -63,15 +66,82 @@ _draw_tt_bt(tt, img)

if (bt is not None): _draw_tt_bt(bt, img, bottom=True) - h_size = int(float(img.size[1]) * (BASE_WIDTH/2) / img.size[0]) - img = img.resize((int(BASE_WIDTH/2), h_size)) + 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 + +def splash_effect(text: str, img: Image): + LETTER_SPACING = 1 + LINE_SPACING = 3 + FILL = (255, 255, 255) + STROKE_WIDTH = 1 + STROKE_FILL = (0, 0, 0) + FONT_FIRST = 50 + FONT_BASE = 75 + MARGIN = 10 + LINE_WIDTH = 20 + + def _draw_splash(text, img): + font_first = ImageFont.truetype(font=ARIAL_FONT_FILE, size=FONT_FIRST) + font_base = ImageFont.truetype(font=ARIAL_FONT_FILE, size=FONT_BASE) + + img_width, img_height = img.size + + d = ImageDraw.Draw(img) + + _, _, first_txt_width, first_txt_height = d.textbbox((0, 0), text[0], font=font_first) + _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[1], font=font_base) + + total_height = (txt_height + LINE_SPACING) * (len(text) - 1) + LINE_SPACING + first_txt_height + y = (img_height - total_height) / 2 + + for i in range(1, len(text)): + #temp = d.textbbox((0, 0), text[i], font=font_base)[2] + temp = int(font_base.getlength(text[i])) + if temp > max_txt_width: + max_txt_width = temp + + max_txt_width = max_txt_width if max_txt_width > first_txt_width else first_txt_width + x_start = (img_width - max_txt_width) / 2 + + for i in range(len(text)): + line = text[i] + x = x_start + ''' + if align == "center": + txt_width = d.textbbox((0, 0), line, font=font)[2] + x = (img_width - txt_width - (len(line) * LETTER_SPACING))/2 + ''' + font = font_base if i > 0 else font_first + _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL) + + y += (txt_height if i > 0 else first_txt_height) + LINE_SPACING + + lines = [x for x in text.split("\n") if x] + if len(lines) < 2: + return img + + img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0])))) + + img = _darken_image(img) + + split_text = textwrap.wrap(lines[1].upper(), width=LINE_WIDTH) + if split_text == []: + return + split_text.insert(0, lines[0]) + _draw_splash(split_text, img) + + 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 -def test(text, effect, modifier=""): +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])))

@@ -79,9 +149,16 @@ 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) + + print("Image test successful") + def main(): - test("top text\nbottom text", tt_bt_effect) - test("top text top text top text top text top text\nbottom text bottom text bottom text bottom text bottom text", tt_bt_effect, "_long") + #test("Autore\ntesto un po' più lungo ma non troppo eh\nquesto verrà scartato\npure questo", splash_effect) + test_multiple("top text\nbottom text", splash_effect) + test_multiple("top text top text top text top text top text\nbottom text bottom text bottom text bottom text bottom text", splash_effect, "_long") if __name__ == "__main__": 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 +from Effects import tt_bt_effect, splash_effect import Constants as C from Constants import get_localized_string as l

@@ -49,7 +49,6 @@

image = None if len(input.photo) > 0: image = input.photo[-1].get_file() - #image = context.bot.get_file(input.photo[-1].file_id) image = Image.open(BytesIO(image.download_as_bytearray())) if input.caption is not None:

@@ -143,11 +142,60 @@ markup = InlineKeyboardMarkup([[InlineKeyboardButton(text=l("sauce", lang), url=url)]])

update.message.reply_photo(photo=image, caption=url, parse_mode="markdown", reply_markup=markup) +def _get_content(message): + image = None + if len(message.photo) > 0: + image = message.photo[-1].get_file() + image = Image.open(BytesIO(image.download_as_bytearray())) + + if message.text is not None: + content = message.text.strip() + elif message.caption is not None: + content = message.caption.strip() + + logging.info(f"User {message.from_user.username} typed: {str(content)}") + + lines = content.split("\n") + r = lines[0].split(" ") + r.pop(0) + lines[0] = " ".join(r) + content = "\n".join(lines) + ''' + lines_new = [] + for line in lines: + words = line.split(" ") + lines_new.append(" ".join(words)) + content = "\n".join(lines_new) + ''' + + return image, content + +def _format_author(user): + if user.username is not None: + return user.full_name + f" ({user.username})" + return user.full_name + +def _get_author(message): + + if message.reply_to_message is None: + return _format_author(message.from_user) + + if message.reply_to_message.forward_from is not None: + return _format_author(message.reply_to_message.forward_from) + + return _format_author(message.reply_to_message.from_user) + def tt(update: Update, context: CallbackContext): - image, reply = _get_reply(update.message.reply_to_message, context) - content = _get_args(context) + image_reply, reply = _get_reply(update.message.reply_to_message, context) + image_content, content = _get_content(update.message) input_text = f"{reply} {content}".replace("\n", " ") + image = None + if image_reply is not None: + image = image_reply + + if image_content is not None: + image = image_content image, markup = _ttbt_general(context, input_text, image) if image is None:

@@ -156,10 +204,16 @@ return

update.message.reply_photo(photo=image, parse_mode="markdown", reply_markup=markup) def bt(update: Update, context: CallbackContext): - image, reply = _get_reply(update.message.reply_to_message, context) - content = _get_args(context) + image_reply, reply = _get_reply(update.message.reply_to_message, context) + image_content, content = _get_content(update.message) input_text = f"{reply} {content}".replace("\n", " ") + image = None + if image_reply is not None: + image = image_reply + + if image_content is not None: + image = image_content image, markup =_ttbt_general(context, " \n" + input_text, image) if image is None:

@@ -168,22 +222,56 @@ return

update.message.reply_photo(photo=image, parse_mode="markdown", reply_markup=markup) def ttbt(update: Update, context: CallbackContext): - message = update.message - - image, reply = _get_reply(message.reply_to_message, context) - content = message.text.split(" ") - content.pop(0) - content = " ".join(content) + image_reply, reply = _get_reply(update.message.reply_to_message, context) + image_content, content = _get_content(update.message) input_text = f"{reply}\n{content}" + image = None + if image_reply is not None: + image = image_reply + + if image_content is not None: + image = image_content + image, markup =_ttbt_general(context, input_text, image) if image is None: - message.reply_text(markup) + update.message.reply_text(markup) return - message.reply_photo(photo=image, parse_mode="markdown", reply_markup=markup) + update.message.reply_photo(photo=image, parse_mode="markdown", reply_markup=markup) + +def splash(update: Update, context: CallbackContext): + author = _get_author(update.message) + image_reply, reply = _get_reply(update.message.reply_to_message, context) + image_content, content = _get_content(update.message) + input_text = f"{author}\n{reply}\n{content}" + + markup = "" + + if len(input_text.strip().split("\n")) < 2: + markup = l("no_caption", lang) + update.message.reply_text(markup) + return + + image = None + if image_reply is not None: + image = image_reply + + if image_content is not None: + image = image_content + + if image is None: + image, markup = _get_image(context, bio=False) + image = _img_to_bio(splash_effect(input_text, image)) + + if image is None: + update.message.reply_text(markup) + return + + update.message.reply_photo(photo=image, parse_mode="markdown", reply_markup=markup) + def caps(update: Update, context: CallbackContext): _, reply = _get_reply(update.message.reply_to_message, context, _get_args(context)) context.bot.send_message(chat_id=update.effective_chat.id, text=reply.upper())

@@ -199,6 +287,11 @@ # logging.error("BadRequest!!")

except TelegramError: logging.error("TelegramError!!") context.bot.send_message(chat_id=update.effective_chat.id, text=l('error', lang)) + +def _add_effect_handler(dispatcher, command: str, callback): + dispatcher.add_handler(CommandHandler(command, callback)) + + dispatcher.add_handler(MessageHandler(Filters.caption(update=[f"/{command}"]), callback)) def main(): updater = Updater(token=os.getenv("token"))

@@ -211,9 +304,11 @@ dispatcher.add_handler(CommandHandler('caps', caps))

dispatcher.add_handler(CommandHandler('pic', pic)) dispatcher.add_handler(CommandHandler('raw', raw)) dispatcher.add_handler(CommandHandler('pilu', pilu)) - dispatcher.add_handler(CommandHandler('ttbt', ttbt)) - dispatcher.add_handler(CommandHandler('tt', tt)) - dispatcher.add_handler(CommandHandler('bt', bt)) + + _add_effect_handler(dispatcher, 'ttbt', ttbt) + _add_effect_handler(dispatcher, 'tt', tt) + _add_effect_handler(dispatcher, 'bt', bt) + _add_effect_handler(dispatcher, 'splash', splash) dispatcher.add_handler(MessageHandler(Filters.command, unknown)) updater.start_polling()