Search code examples
pythonpython-imaging-librarytelegramtelegram-bot

How to adjust the image to meet the minimum requirements and avoid receiving the Telegram API Bad Request error: PHOTO_INVALID_DIMENSIONS?


Summary: Telegram has requirements for images that are sent, and to avoid failures, I add huge white borders far above what would be the minimum necessary. I would like to know how I could create a method so that the image is adjusted at once but with the minimum borders required.

According to the documentation I couldn't understand exactly how I could reach that value, here it is sendPhoto:

Photo to send. Pass a file_id as a String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20.

My sample code creates this image that generates an error when sending it to Telegram because it does not meet the minimum requirements:

enter image description here

Currently, the code adds borders above and below a size that I know works, and it looks like this:

def create_image():
    df = pd.DataFrame(columns=[f"column_letter_{chr(97 + i)}" for i in range(9)])
    dfi.export(df, 'telegram_image.png', table_conversion="selenium", max_rows=-1)

def adjust_image():
    fil_img = Image.open("telegram_image.png")
    min_img = 600
    difference = min_img - fil_img.size[1]
    new_image = Image.new("RGB", (fil_img.size[0], min_img), (255, 255, 255))
    new_image.paste(fil_img, (0, difference // 2))
    new_image.save("telegram_image.png")

enter image description here

In this additional method I tried, the problem is that the image shrinks and thus the quality also decreases, I don't want the quality to decrease at all, I just want to adjust the edges so that it maintains the original size of the image but fits the minimum necessary proportion:

def adjust_image():
    image = Image.open('telegram_image.png')
    width, height = image.size

    if width / height > 20 or height / width > 20:
        new_width = min(width, height * 20)
        new_height = min(height, width * 20)
        image = image.resize((new_width, new_height), Image.ANTIALIAS)
        image.save('telegram_image.png', quality=100, subsampling=0)

enter image description here

How would it be possible to create an image adjustment that is not fixed to a specific number of borders to add, and instead adds only the minimum amount necessary?

Complete Code:

import dataframe_image as dfi
from PIL import Image
import pandas as pd
import requests
import json

def send_image_telegram(ids_chat, bot_token, text_msg, file_photo, buttons=None, parse_mode=None):
    if not isinstance(ids_chat, list):
        ids_chat = [ids_chat]
    list_for_return = []
    for chat_id in ids_chat:
        telegram_api_url = f'https://api.telegram.org/bot{bot_token}/sendPhoto'
        message_data = {
            "chat_id": chat_id,
            "caption": text_msg
        }
        if parse_mode is not None:
            message_data['parse_mode'] = parse_mode
        if buttons is not None:
            message_data['reply_markup'] = json.dumps({'inline_keyboard': buttons})
        with open(file_photo, "rb") as imageFile:
            response = requests.post(telegram_api_url, files={"photo": imageFile}, data=message_data)
            list_for_return.append(json.loads(response.text))
    return list_for_return

def create_image():
    df = pd.DataFrame(columns=[f"column_letter_{chr(97 + i)}" for i in range(9)])
    dfi.export(df, 'telegram_image.png', table_conversion="selenium", max_rows=-1)

def adjust_image():
    fil_img = Image.open("telegram_image.png")
    min_img = 600
    difference = min_img - fil_img.size[1]
    new_image = Image.new("RGB", (fil_img.size[0], min_img), (255, 255, 255))
    new_image.paste(fil_img, (0, difference // 2))
    new_image.save("telegram_image.png")

def to_telegram():
    send = send_image_telegram('123456789','XXXXXXXXXX','test','telegram_image.png')
    if not send[0]['ok'] and send[0]['description'] == 'Bad Request: PHOTO_INVALID_DIMENSIONS':
        adjust_image()
        send_image_telegram('123456789','XXXXXXXXXX','test','telegram_image.png')

def main():
    create_image()
    to_telegram()

if __name__ == '__main__':
    main()

Solution

  • I don't use Telegram and am not 100% sure I understand your question, but in a nutshell it seems you want to make a 1157x23 image comply with having an aspect ratio less than 20. If you do that by resizing, you will distort the image, so I am suggesting you do it by padding instead - I'm doing it in magenta so you can see what I have added:

    #!/usr/bin/env python3
    
    from math import ceil
    from PIL import Image
    
    # Open original image and get its size
    im = Image.open('7r8q3.png')
    w, h = im.size
    
    # Calculate new size to comply with a 1:20 aspect ratio
    newH = ceil(w/20)
    
    # Create background of new image
    padded = Image.new('RGB', (w,newH), 'magenta')
    
    # Paste existing image into new one at vertical midpoint
    padded.paste(im, (0,int((newH-h)/2)))
    padded.save('result.jpg')
    

    enter image description here

    The new image has size 1157x58, which is an aspect ratio of 19.95:1. Obviously you can change the magenta to any other colour you like once you have understood my suggestion.