[Python] Créez un linebot qui dessine n'importe quelle date sur une photo

Ce que j'ai fait

Lorsque j'envoie une image, il renvoie une action de sélection de date et crée un robot de ligne qui dessine la date sélectionnée sur l'image. En gros, je viens de permettre de sélectionner moi-même la partie date de cet article, mais comme il y avait différents points d'achoppement, je vais le résumer cette fois. Référence: [Python] J'ai créé un Bot LINE qui date les photos

environnement

Ce qu'il ne faut pas écrire dans cet article

--Création de canaux LineBot --Déployer sur heroku

Texte intégral

main.py

main.py


from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (PostbackEvent, TemplateSendMessage, ButtonsTemplate, DatetimePickerTemplateAction,
                            ImageMessage, ImageSendMessage, MessageEvent, TextMessage, TextSendMessage)

from pathlib import Path
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import datetime
import os
import re

app = Flask(__name__)
app.debug = False

#Obtenir des variables d'environnement
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]

line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)

#Chemin de référence de l'image
SRC_IMAGE_PATH = "static/images/{}.jpg "
MAIN_IMAGE_PATH = "static/images/{}_main.jpg "
PREVIEW_IMAGE_PATH = "static/images/{}_preview.jpg "

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

#Suivez l'événement
@handler.add(FollowEvent)
def handle_follow(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=
        "Merci de vous être inscrit comme ami. Si vous envoyez une image et me dites la date de prise de vue, j'écrirai cette date sur l'image"))

#Retourner le perroquet de texte
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))

#Recevoir des images
@handler.add(MessageEvent, message=ImageMessage)
def get_image(event):
    global message_id

    #message_Obtenir l'identifiant
    message_id = event.message.id
    
    #Message de nom de fichier_Chemin converti en identifiant
    src_image_path = Path(SRC_IMAGE_PATH.format(message_id)).absolute()

    #Enregistrer temporairement les images sur heroku
    save_image(message_id, src_image_path)
    
    #Enregistrer en tant qu'image à afficher lors de la sélection de la date et de l'heure
    im = Image.open(src_image_path)
    im.save(src_image_path)
    
    #Sélection de la date de prise de vue
    date_picker = TemplateSendMessage(
        alt_text='Veuillez sélectionner la date de prise de vue',
        template=ButtonsTemplate(
            text='Veuillez sélectionner la date de prise de vue',
            thumbnail_image_url=f"https://<nom de l'application heroku>.herokuapp.com/{src_image_path}",
            actions=[
                DatetimePickerTemplateAction(
                    label='Choix',
                    data='action=buy&itemid=1',
                    mode='date',
                    initial=str(datetime.date.today()),
                    max=str(datetime.date.today())
                )
            ]
        )
    )
    
    line_bot_api.reply_message(
        event.reply_token,
        date_picker
    )

#Traiter et envoyer des images
@handler.add(PostbackEvent)
def handle_postback(event):
    #Message de nom de fichier_Chemin converti en identifiant
    src_image_path = Path(SRC_IMAGE_PATH.format(message_id)).absolute()
    main_image_path = MAIN_IMAGE_PATH.format(message_id)
    preview_image_path = PREVIEW_IMAGE_PATH.format(message_id)
    
    #Traitement d'image
    date_the_image(src_image_path, Path(main_image_path).absolute(), event)
    date_the_image(src_image_path, Path(preview_image_path).absolute(), event)

    #Envoyer l'image
    image_message = ImageSendMessage(
            original_content_url=f"https://<nom de l'application heroku>.herokuapp.com/{main_image_path}",
            preview_image_url=f"https://<nom de l'application heroku>.herokuapp.com/{preview_image_path}"
    )
    
    #Obtenir le journal
    app.logger.info(f"https://<nom de l'application heroku>.herokuapp.com/{main_image_path}")
    
    line_bot_api.reply_message(event.reply_token, image_message)

#Fonction de stockage d'image
def save_image(message_id: str, save_path: str) -> None:
    # message_Obtenir les données binaires de l'image à partir de l'identifiant
    message_content = line_bot_api.get_message_content(message_id)
    with open(save_path, "wb") as f:
        #Ecrire les données binaires acquises
        for chunk in message_content.iter_content():
            f.write(chunk)

#Fonction de traitement d'image
def date_the_image(src: str, desc: str, event) -> None:
    im = Image.open(src)
    draw = ImageDraw.Draw(im)
    font = ImageFont.truetype("./fonts/Helvetica.ttc", 50)
    #Obtenir la date de l'action de sélection de la date et de l'heure
    text = event.postback.params['date']
    #Remplacement de chaîne par une expression régulière
    text_mod = re.sub("-", "/", text)
    #Taille du texte
    text_width = draw.textsize(text_mod, font=font)[0]
    text_height = draw.textsize(text_mod, font=font)[1]
    
    margin = 10
    x = im.width - text_width
    y = im.height - text_height
    #La taille du rectangle à dessiner
    rect_size = ((text_width + margin * 6), (text_height + margin * 2))
    #Dessinez un rectangle
    rect = Image.new("RGB", rect_size, (0, 0, 0))
    #Masque pour rendre le rectangle transparent
    mask = Image.new("L", rect_size, 128)
    
    #Coller un rectangle et un masque sur l'image
    im.paste(rect, (x - margin * 6, y - margin * 3), mask)
    #Rédaction de texte
    draw.text((x - margin * 3, y - margin * 2), text_mod, fill=(255, 255, 255), font=font)
    im.save(desc)

if __name__ == "__main__":
    #app.run()
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port)

Commentaire

Préparation

from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (PostbackEvent, TemplateSendMessage, ButtonsTemplate, DatetimePickerTemplateAction,
                            ImageMessage, ImageSendMessage, MessageEvent, TextMessage, TextSendMessage)

from pathlib import Path
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import datetime
import os
import re

app = Flask(__name__)
app.debug = False

#Obtenir des variables d'environnement
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]

line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)

#Chemin de référence de l'image
SRC_IMAGE_PATH = "static/images/{}.jpg "
MAIN_IMAGE_PATH = "static/images/{}_main.jpg "
PREVIEW_IMAGE_PATH = "static/images/{}_preview.jpg "

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

Importez le module et obtenez les variables d'environnement définies à l'avance, mais veuillez vérifier le rôle détaillé le cas échéant. Définissez le chemin de la source de référence d'image décrite plus loin et créez une liste vide pour stocker ~~ message_id. ~~ Remplacez la partie {} par message_id lorsque l'image a été reçue.

Envoyer du texte lorsque vous suivez

#Suivez l'événement
@handler.add(FollowEvent)
def handle_follow(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=
        "Merci de vous être inscrit comme ami. Si vous envoyez une image et me dites la date de prise de vue, j'écrirai cette date sur l'image"))

Un message est envoyé lorsqu'un utilisateur ajoute un ami.

Retourner le perroquet de texte

#Retourner le perroquet de texte
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))

Je vais retourner le texte perroquet, mais ce n'est pas nécessaire.

Recevoir des images

#Recevoir des images
@handler.add(MessageEvent, message=ImageMessage)
def get_image(event):
    global message_id

    #message_Obtenir l'identifiant
    message_id = event.message.id
    
    #Message de nom de fichier_Chemin converti en identifiant
    src_image_path = Path(SRC_IMAGE_PATH.format(message_id)).absolute()

    #Enregistrer temporairement les images sur heroku
    save_image(message_id, src_image_path)
    
    #Enregistrer en tant qu'image à afficher lors de la sélection de la date et de l'heure
    im = Image.open(src_image_path)
    im.save(src_image_path)
    
    #Sélection de la date de prise de vue
    date_picker = TemplateSendMessage(
        alt_text='Veuillez sélectionner la date de prise de vue',
        template=ButtonsTemplate(
            text='Veuillez sélectionner la date de prise de vue',
            thumbnail_image_url=f"https://<nom de l'application heroku>.herokuapp.com/{src_image_path}",
            actions=[
                DatetimePickerTemplateAction(
                    label='Choix',
                    data='action=buy&itemid=1',
                    mode='date',
                    initial=str(datetime.date.today()),
                    max=str(datetime.date.today())
                )
            ]
        )
    )
    
    line_bot_api.reply_message(
        event.reply_token,
        date_picker
    )

ʻEvent.message_idobtient l'identifiant attribué à chaque message et le définit globalement pour une utilisation dans d'autres événements. .. ~~ Cet identifiant ne peut pas être obtenu dans d'autres événements, alors stockez-le dans laliste_messagesvide créée à l'avance. ~~ Après avoir temporairement enregistré les données dans heroku, ouvrez les données enregistrées pour les afficher pendant l'action de sélection de la date et enregistrez-les à nouveau en tant qu'image. Lors du retour de l'action de sélection de date avecTemplateSendMessage et en demandant à l'utilisateur de sélectionner la date de prise de vue, elle sera affichée si l'URL de la destination d'enregistrement est spécifiée avec thumbnail_image_url`.

Transmission d'image

#Traiter et envoyer des images
@handler.add(PostbackEvent)
def handle_postback(event):
    #Message de nom de fichier_Chemin converti en identifiant
    src_image_path = Path(SRC_IMAGE_PATH.format(message_id)).absolute()
    main_image_path = MAIN_IMAGE_PATH.format(message_id)
    preview_image_path = PREVIEW_IMAGE_PATH.format(message_id)
    
    #Traitement d'image
    date_the_image(src_image_path, Path(main_image_path).absolute(), event)
    date_the_image(src_image_path, Path(preview_image_path).absolute(), event)

    #Envoyer l'image
    image_message = ImageSendMessage(
            original_content_url=f"https://<nom de l'application heroku>.herokuapp.com/{main_image_path}",
            preview_image_url=f"https://<nom de l'application heroku>.herokuapp.com/{preview_image_path}"
    )
    
    #Obtenir le journal
    app.logger.info(f"https://<nom de l'application heroku>.herokuapp.com/{main_image_path}")
    
    line_bot_api.reply_message(event.reply_token, image_message)

Obtient l'image reçue dans MessageEvent (ImageMessage) mentionné ci-dessus. ~~ Obtenez l'id qui sera le nom de l'image à partir de message_list pour le déterminer. Si les images sont envoyées en continu, le nouvel identifiant sera stocké avant que les données ne soient initialisées, alors spécifiez le dernier (le plus récent) avec message_list [-1]. Récupérez la date sélectionnée par l'utilisateur avec handl_postback (event) et assignez-la à text de la fonction data_the_image pour écrire du texte, etc. Lorsque le processus est terminé, enregistrez l'image et renvoyez-la à l'utilisateur pour terminer.

Fonction de stockage d'image

#Fonction de stockage d'image
def save_image(message_id: str, save_path: str) -> None:
    # message_Obtenir les données binaires de l'image à partir de l'identifiant
    message_content = line_bot_api.get_message_content(message_id)
    with open(save_path, "wb") as f:
        #Ecrire les données binaires acquises
        for chunk in message_content.iter_content():
            f.write(chunk)

En fait, je voulais obtenir les informations Exif de l'image et entrer automatiquement la date de prise de vue, mais je ne pouvais pas l'obtenir avec cette méthode, j'ai donc pris la forme de l'action de sélection de la date ci-dessus comme une mesure minutieuse. Si quelqu'un sait comment le faire, faites-le moi savoir.

Fonction de traitement d'image

#Fonction de traitement d'image
def date_the_image(src: str, desc: str, event) -> None:
    im = Image.open(src)
    draw = ImageDraw.Draw(im)
    font = ImageFont.truetype("./fonts/Helvetica.ttc", 50)
    #Obtenir la date de l'action de sélection de la date et de l'heure
    text = event.postback.params['date']
    #Remplacement de chaîne par une expression régulière
    text_mod = re.sub("-", "/", text)
    #Taille du texte
    text_width = draw.textsize(text_mod, font=font)[0]
    text_height = draw.textsize(text_mod, font=font)[1]
    
    margin = 10
    x = im.width - text_width
    y = im.height - text_height
    #La taille du rectangle à dessiner
    rect_size = ((text_width + margin * 6), (text_height + margin * 2))
    #Dessinez un rectangle
    rect = Image.new("RGB", rect_size, (0, 0, 0))
    #Masque pour rendre le rectangle transparent
    mask = Image.new("L", rect_size, 128)
    
    #Coller un rectangle et un masque sur l'image
    im.paste(rect, (x - margin * 6, y - margin * 3), mask)
    #Rédaction de texte
    draw.text((x - margin * 3, y - margin * 2), text_mod, fill=(255, 255, 255), font=font)
    im.save(desc)

Je voulais le rendre beau dans une certaine mesure, alors j'ai ajouté un peu de traitement. ʻEvent.postback.params ['date'] pour obtenir la date sélectionnée par l'utilisateur, et re.sub ("-", "/", text) pour changer" YYYY-MM-DD "en" YYYY / Convertissez au format "MM / JJ". Utilisez ((text_width + margin * 6), (text_height + margin * 2)) pour définir la taille du rectangle et du masque, et laissez une marge de 30px à gauche et à droite et 10px en haut et en bas du texte. Enfin, spécifiez la position du rectangle et du masque avec ʻim.paste (rect, (x --margin * 6, y --margin * 3), mask) ʻet collez-le, et draw.text ( Écrivez le texte avec (x --margin * 3, y --margin * 2), text_mod, fill = (255, 255, 255), font = font) `et vous avez terminé.

Courir

if __name__ == "__main__":
    #app.run()
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port)

organisation des fichiers

Résumé

Ce bot de ligne a été la première application que j'ai créée après avoir étudié par moi-même, mais il a fallu environ six mois (environ 1 à 2 heures par jour en moyenne) pour y parvenir. Environ 80% du temps de production a été consacré à enquêter sur ce que je ne comprenais pas, et il y avait des moments où c'était assez difficile. En fait, il y a eu des moments où je n'ai pas ouvert mon ordinateur pendant environ une semaine, mais je n'ai jamais pensé à abandonner la programmation, alors je pense que je m'amusais à le faire.

En fait, il y a eu des rebondissements depuis la première chose que je voulais faire, et je me suis installé sur cette forme, mais j'ai appris qu'il est très important de faire tout le flux à l'avance dans une certaine mesure. Cependant, il est difficile de comprendre quel genre de choses peut être fait avec combien d'efforts avec peu d'expérience, donc à la fin je pense qu'il n'y a pas d'autre choix que de procéder régulièrement par essais et erreurs.

Actuellement, j'étudie la base de données et j'essaie d'enregistrer le nom et la date de naissance de la personne sur la photo afin de pouvoir calculer à partir de la date de prise de vue et afficher l'âge de la photo, dès qu'elle est terminée Je vais le publier.

référence

Recommended Posts

[Python] Créez un linebot qui dessine n'importe quelle date sur une photo
[Python] Créez un LineBot qui s'exécute régulièrement
Créer un environnement Python sur Mac (2017/4)
Créer un environnement python dans centos
Créez un environnement python sur votre Mac
[Python] Créez un linebot pour écrire le nom et l'âge sur l'image
Créer une page qui se charge indéfiniment avec python
[Venv] Créer un environnement virtuel python sur Ubuntu
Python: créer une classe qui prend en charge l'affectation décompressée
Créer un environnement d'exécution Python sur IBM i
Créer un module Python
Créer un environnement Python
Créez un environnement de développement Python 3 (Anaconda) confortable avec Windows
En Python, créez un décorateur qui accepte dynamiquement les arguments Créer un décorateur
[python] Créez un tableau de dates avec des incréments arbitraires avec np.arange
Créez un environnement shell et python décent sur Windows
Créer un environnement de développement Python avec OS X Lion
Créer un plugin Wox (Python)
Créer une fonction en Python
Créer un dictionnaire en Python
Créer un environnement de développement Python (pyenv / virtualenv) sur Mac (Homebrew)
Boucle sur un générateur qui renvoie un itérateur de date en Python
Créons un script qui s'enregistre avec Ideone.com en Python.
[Python] Créer une liste de dates et d'heures pour une période spécifiée
Créer un tableau numpy python
Créez le code qui renvoie "A et prétendant B" en python
Créer un script Python pour Wake on LAN (Wake on LAN over NAT [5])
Créer une salle de classe sur Jupyterhub
Créer un répertoire avec python
Créer un environnement virtuel pour python sur mac [Très facile]
Concurrence avec VS Code Créez un environnement Python pour les professionnels sous Windows
Facile! Implémenter un bot Twitter qui s'exécute sur Heroku en Python
[Python / Django] Créer une API Web qui répond au format JSON
[Ev3dev] Créez un programme qui capture LCD (écran) en utilisant python
[LINE Messaging API] Créez un BOT qui se connecte à quelqu'un avec Python
J'essaierai de créer une structure de répertoires Python que je ne regretterai pas plus tard
Python vba pour créer une chaîne de date pour créer un nom de fichier
Un script python qui supprime les fichiers ._DS_Store et ._ * créés sur Mac
Construire un environnement Python sur Mac
J'ai fait un Line-bot avec Python!
Créer une interface graphique python à l'aide de tkinter
Créer un conteneur DI avec Python
Construire un environnement Python sur Ubuntu
Créez un environnement virtuel avec Python!
Créer un fichier binaire en Python
Créer un service SlackBot sur Pepper
Créer un environnement Linux sur Windows 10
Créer un framework de décorateur à usage général pour Python
5 façons de créer un chatbot Python
Créer un environnement python3 sur CentOS7
Créer une chaîne aléatoire en Python
Présentation d'une bibliothèque qui n'était pas incluse dans pip sur Python / Windows
[Python] J'ai fait un décorateur qui ne semble pas avoir d'utilité.
Créez un environnement de développement Python sur Windows (WSL distant Visual Studio Code).
Procédure de création d'un environnement virtuel Python avec VS Code sous Windows
Créez et modifiez des feuilles de calcul dans n'importe quel dossier sur Google Drive avec python
Créez une plateforme multi-utilisateurs Python avec JupyterHub + JupyterLab sur Rapsberry Pi 3B +!