めもちょー

メモ帳代わりに使っています。

ブログに上げる画像の加工サーバーを作った

旅行記を書いているのですが、流石に何百枚とアップロードしているのに無加工は厳しいと思い画像の右下にロゴを入れる簡易アプリケーションを作りました。
写真の取り込みはWindowsで行うため、ローカル環境でWindowマシンからアクセスし、Ubuntu上で加工ができるアプリケーションを作成しました。
まず下記のような画像をアップロードします。


するとHTMLで次の画像が表示されます。

サーバー上では、保存を行うようにしています。

コードは下記の通りです。
Fast APIでリクエストを処理し、OpenCVとPILで画像加工を行っています。

import base64
from datetime import datetime
from io import BytesIO
from typing import Annotated

import cv2
import numpy as np
import uvicorn
from fastapi import FastAPI, File, Request, UploadFile
from fastapi.responses import HTMLResponse
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont

app = FastAPI()

OUTPUT_DIR = Path("output")

@app.post("/files/")
async def create_files(files: Annotated[list[bytes], File()]):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(files: list[UploadFile]):
    return {"filenames": [file.filename for file in files]}


@app.post("/add_logo/")
async def add_logo(files: list[UploadFile]):
    # uploadされた画像を読み込む
    file = files[0]
    content = await file.read()
    original_image = Image.open(BytesIO(content))

    # 画像に文字を追加
    processed_image = add_text_to_image(original_image, "gesoges0")

    # 加工された画像を保存
    save_image(processed_image, file.filename)

    # 加工された画像をBase64形式にエンコードしHTMLに表示
    buffered = BytesIO()
    processed_image.save(buffered, "PNG")
    processed_image_data = base64.b64encode(buffered.getvalue()).decode("utf-8")

    # 加工された画像をBase64形式にエンコードしてHTMLに表示
    encoded_image = f"data:image/png;base64,{processed_image_data}"

    return HTMLResponse(content=f"<img src='{encoded_image}'/ width=50%>")


def add_text_to_image(image, text):
    """画像にtextを書き込む"""
    # PIL ImageをOpenCV形式に変換
    cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)

    # 文字を追加
    font = ImageFont.truetype("arial.ttf", 30)
    img_pil = Image.fromarray(cv_image)
    draw = ImageDraw.Draw(img_pil)
    w, h = img_pil.size
    draw.text((w - 150, h - 50), text, font=font, fill=(255, 255, 255))

    # OpenCV形式をPIL Imageに戻す
    processed_image = cv2.cvtColor(np.array(img_pil), cv2.COLOR_BGR2RGB)
    return Image.fromarray(processed_image)


def save_image(image, name):
    """save PIL Image"""
    # 本日の日付
    today = datetime.today().strftime("%Y%m%d")
    save_dir = OUTPUT_DIR / today
    if not save_dir.exists():
        save_dir.mkdir()
    file_path = save_dir / name
    print(f"saved: {file_path}")
    image.save(file_path)


@app.get("/")
async def main():
    content = """
<body>
<form action="/add_logo/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

実行は通常のPythonプログラムと同様です。

python main.py