今回はブログに載せる写真にウォーターマークを入れる作業を手動から自動化したので、そちらについて書いていこうと思います。
今まで手動で写真のリサイズとウォーターマークを入れていたのですが、手動で行うのはやっぱり手間で、たまに外食した写真でブログを書こうと思っても、RAW現像まではしても編集する気までは起きず、結局書かずに終わることがほとんどでした。
自動化の方法を調べても以前は理解できず、今も理解できているわけではないものの、生成AIに聞けば、Pythonを理解しなくても画像の一括処理を行うスクリプトを作成してくれたりと、以前よりも自動化のハードルは下がったので、今回試してみることにしました。
今回はPythonというAIの動画を見ていると言葉を聞くことがあるけれど、どういうものか自分には理解できていないプログラミング言語を使って行いました。
自分がPythonというものを理解していなかったので、1回では上手くいかず、2時間ぐらいかかったものの、当時挫折してしまったことを思うと、何日かけてもできなかったことが、たった数時間でできるようになったことにびっくりです。
試行錯誤の結果、特定のフォルダ(赤枠のフォルダ)に保存した画像を2MBにリサイズし、ロゴマーク(オレンジの枠のロゴ)を合成して保存用フォルダ(青枠のフォルダ)に保存することができるようになりました。
無事にリサイズと透かし入れができるようになったので、最近食べた天丼と蕎麦の写真を早速保存してみました。
写真を保存してすぐに保存用フォルダを確認したところ、数枚程度の写真であれば、一瞬で処理が完了していました。
今後は先に一括処理を行ってから、編集が必要な写真だけをAffinity Photoで編集すれば、画像のデータが小さくなるので、不要なメモリを使わずに済むかもしれないと思いました。
一括処理をした画像がちゃんと2MB以下にリサイズできているか確認するため、画像をアップロードしてみました。
ちゃんと2MB以下にリサイズされていたので、特に編集しなくても問題なくアップロードすることができました。
ロゴマークの位置も思った場所に置くことができ、サイズも最初大きくなってしまったのですが、コードの数字を書き換えることで簡単に想像通りのサイズに変更することができました。
数枚だとリサイズも透かし入れもそこまで時間のかかる作業ではないものの、ファイルに保存するだけで10秒もかからずに終わってしまったため、一括処理ってこんなに便利だったのかと感動しました。
Pythonを使った自動化スクリプトの作成は数ヶ月ほど前にローカルLLMを色々と試していた頃、Obsidian Web Clipperで保存した記事の内容を要約させ、別のフォルダに保存させる作業を自動化した時に使ったのですが、やり方をすっかり忘れました。
コードなどはGeminiに書いてもらうので、良いのですが、せめてどこに入力するかぐらいは覚えておきたいので、最後にメモを残そうと思います。
下準備として画像処理用ツールをターミナルに下記のコードを入力してダウンロードするとのこと。
pip3 install pillow watchdog
それから、下記のコードをテキストエディタというWindowsのメモ帳みたいなアプリを開いて貼り付け。
import os
import time
from PIL import Image, ImageEnhance, ImageOps
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
# --------------------------------------------------
# パス設定(環境に合わせて修正してください)
# --------------------------------------------------
WATCH_DIR = "/Users/NAME/Pictures/SILKYPIX" # 監視対象フォルダ
OUTPUT_DIR = "/Users/NAME/Pictures/Ameba" # 保存先フォルダ
LOGO_PATH = "/Users/NAME/Pictures/logo.png" # 使用するロゴ画像 (透過PNG)
# 設定値
TARGET_SIZE_BYTES = 2 * 1024 * 1024 # 2MB
LOGO_RATIO = 0.12 # 写真の横幅に対するロゴの大きさ (例: 15%)
MARGIN_PX = 20 # 端からの余白 (px)
def process_and_save_image(src_path):
"""画像を処理して2MB以下で保存する"""
filename = os.path.basename(src_path)
output_path = os.path.join(OUTPUT_DIR, filename)
# フォルダの自動作成
os.makedirs(OUTPUT_DIR, exist_ok=True)
# SiILKYPIXからの書き出し完了を少し待つ(ファイルロック回避)
time.sleep(2)
try:
with Image.open(src_path) as img:
# 1. Exifに基づく画像の回転補正
img = ImageOps.exif_transpose(img)
working_img = img.convert("RGBA")
# 2. ロゴマークの合成
if os.path.exists(LOGO_PATH):
with Image.open(LOGO_PATH) as logo:
logo = logo.convert("RGBA")
# ロゴのリサイズ(長辺の一定割合に合わせる)
target_logo_width = int(working_img.width * LOGO_RATIO)
logo_aspect = logo.height / logo.width
target_logo_height = int(target_logo_width * logo_aspect)
resized_logo = logo.resize(
(target_logo_width, target_logo_height),
Image.Resampling.LANCZOS,
)
# --- 【変更箇所】左下の配置位置計算 ---
pos_x = MARGIN_PX
pos_y = working_img.height - target_logo_height - MARGIN_PX
# 合成
working_img.paste(
resized_logo, (pos_x, pos_y), resized_logo
)
# 3. RGB変換(JPEG保存用)
final_img = working_img.convert("RGB")
# 4. 2MB以下に収める品質・サイズ自動調整
quality = 95
while quality >= 30:
final_img.save(output_path, "JPEG", quality=quality)
file_size = os.path.getsize(output_path)
if file_size <= TARGET_SIZE_BYTES:
print(
f"成功: {filename} を保存しました ({file_size / (1024*1024):.2f} MB / Quality: {quality})"
)
break
# 容量オーバーの場合は品質を下げる
quality -= 5
# 品質調整のみで2MBを下回らない場合の解像度リサイズ退避処理
if file_size > TARGET_SIZE_BYTES:
final_img.thumbnail(
(working_img.width * 0.8, working_img.height * 0.8),
Image.Resampling.LANCZOS,
)
final_img.save(output_path, "JPEG", quality=85)
print(f"リサイズ適用して2MB以下に収めました: {filename}")
except Exception as e:
print(f"エラー発生 ({filename}): {e}")
class ImageHandler(FileSystemEventHandler):
def on_created(self, event):
if event.is_directory:
return
if event.src_path.lower().endswith(
(".jpg", ".jpeg", ".png", ".tiff")
):
print(f"検知: {event.src_path}")
process_and_save_image(event.src_path)
if __name__ == "__main__":
event_handler = ImageHandler()
observer = Observer()
observer.schedule(event_handler, WATCH_DIR, recursive=False)
print(f"監視開始: {WATCH_DIR}")
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
フォーマットを「⌘+⇧+T」を押して「標準テキスト」へ変更後、名前をつけて保存する際に、拡張子を.pyにして保存。
監視対象のフォルダ、保存用フォルダ、そしてロゴマークのパスはターミナルに貼り付ければ表示されるため、そちらを参照。
Geminiに最初聞いた際、保存先をiCloud上のDocumentsに保存するよう指示があったが、iCloud上のフォルダに保存すると自動化が上手くいかなかったので、PC上に適当にScriptsという名前のフォルダを作成して保存。
その後、Macで常時起動させるために設定ファイルを作るらしく、こちらもテキストエディタを使用して作成。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.autowatermark</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/Users/NAME/Scripts/auto_watermark.py</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
フォーマットを標準テキストに変更し、拡張子を.plistにして保存。
保存場所をFinderを開いて、メニューバーから移動を選び、フォルダへ移動で「~/Library/LaunchAgents」と入力し、LaunchAgentsのフォルダを開いて、そこに作成した設定ファイルを保存。
launchctl load ~/Library/LaunchAgents/com.user.autowatermark.plist
最後に上記のコードを入力すれば終わりだったのですが、ファイルの保存場所を当初iCloud上にしてしまったのが原因なのか、ターミナルで手動でオンにした時しか画像の処理が起きなかったため、ファイルの場所をローカル上へ移動。
launchctl unload ~/Library/LaunchAgents/com.user.autowatermark.plist launchctl load ~/Library/LaunchAgents/com.user.autowatermark.plist
最後に上記のコードを入力し、設定を読み込み直して無事に画像のリサイズと透かし入れを自動でしてくれるようになりました。
とりあえず、今回も長く、上手くまとまらなかったものの、ブログに載せる写真を自動化で一瞬で処理できるようになったので、今後はちょっとしたことでも日記をかけたら良いなと思いつつ、今日は終わろうと思います。





