さっさと本題に入りたい方は目次からどうぞ。
ゲーム画面の静止画をシェアしたいことってありますよね。
今時であればキャプチャー機能が付いているゲームハードは珍しくなく、手軽にSNSへシェアすることもできます。
しかし私は画面全体をキャプチャに含めなくてもいいなというときは、トリミングしてからシェアをしたいと思っていました。
例えば下記投稿はユーザーが書いたイラスト部分が主題であり、ユーザーの装備は画像に含める必要がありません。
すき #Splatoon2 #スプラトゥーン2 #NintendoSwitch pic.twitter.com/wJAowYDEln
— あつし⛅ (@Anaakikutsushit) March 14, 2019
そんな中、ニンテンドーSwitch本体のアップデートでズーム機能が追加されました。
これで必要な部分だけ実質トリミングできる!と思った私でしたが、ズーム機能だけではその願いをかなえることはできませんでした。
ズーム機能は結構可能性を感じます。画像1枚目くらいまではズームすることが可能。ただし、この状態のままスクショボタンを押しても、本体に保存される画像はズームなしの画像(画像2枚目)になります。何かうまい使い道が出てきそうな予感。 pic.twitter.com/ZaTh4yXO9J
— あつし⛅ (@Anaakikutsushit) April 16, 2019
しかしそれでもトリミング画像をシェアしたい私は、ゲーム画面でズームしたままにしておいてキャプチャーボードを起動し、パソコン側でキャプチャーボードに映ったズーム済みの画像をキャプチャーしてSNS投稿用に変換してからシェアするという非常に回りくどい方法を取っていたのです。
キャプチャーするときにフォーマットを選べれば良いのですが、bmp形式での保存しか対応していないのが特にツラいところです(Twitterはbmp画像をアップロードできないっぽい?)。
いい加減面倒な方法だよなと感じたため、キャプボを起動して静止画キャプチャーする手間は減らせないものの、キャプチャー後の手間を減らすために今回のプログラムを作りました。
本題
次のようなプログラムにしました。
- キャプチャーボードからのファイルの保存先フォルダは固定されている。
- 保存先フォルダのファイルを監視し、bmpファイルが作成されたタイミングがわかるようにする。
- 作成されたbmpファイルを縮小し、jpg形式で新規保存する。
フォルダを監視する
watchdogというモジュールを利用します。
下記記事の記述が分かりやすかったため、参考にさせて頂きました。
ファイル監視にwatchdogがかなり便利な件 - Qiita
今回はファイルが作成されたタイミングだけ知ることができればよいので、on_created
のイベントだけ実装します。
ここで一つハマったポイントがありました。
私が利用しているキャプチャーボードAVT-C875
および、その専用ソフトRECentral
からのキャプチャーでファイルが作成されたとき、普通にイベントをハンドルしてしまうとPermissionError
が発生したのです。
なんとなく書けたので実行。しかしpermission errorが出てしまいました。
— あつし🌤️ (@Anaakikutsushit) May 17, 2019
キャプボから作成されたファイルについてはちゃんと補足できているみたいなんですが、なぜpermission error?
デバッグでステップ実行すると同エラーは発生しません。
結果的に、イベントを検知したときにtime.sleep(1)
を入れることで安定して動作するようになりました。
キャプチャーソフトの動作は、ファイルを作成した瞬間にはまだ画像が「完成」しておらず、コントロールを掴んだままになっているのではないかと思っています。
その、まだ未完成の画像ファイルを読もうとしてしまったからエラーになったのではないかという推測ですね。
画像を変換する
PIL
の機能を使って読み込み・リサイズ・保存まで実行します。
画像のキャンバスサイズは整数でなければいけないはずなのでround
関数で丸めました。単に縮小できればよかったので、誤差などは無視しています。
ところで、Image.save()
関数ではフォーマットを指定できる引数が用意されているはずなんですが、format='JPEG'
と指定しても拡張子なしで保存されてしまいました。どうやったらformat
引数にちゃんと保存形式を渡すことができるのでしょうか?ご存知の方がいらっしゃったら教えてください。
Image.save(fp, format=None, **params)
結局、ファイル名 + '.jpg'
というパスを保存先として渡すことでjpg保存させることにしました。
おわり
フォルダ監視って面倒なんじゃないかな~と思っていたんですが、簡単に実装できるモジュールがあって助かりました。
以下、今回作ったプログラムです。
# -*- coding: utf-8 -*-
import time
import os
from PIL import Image
import numpy as np
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
BASEDIR = os.path.abspath("E:\VideoGames\【キャプチャ映像】")
def get_ext(filename):
return os.path.splitext(filename)[-1].lower()
class ChangeHandler(FileSystemEventHandler):
def on_created(self, event):
time.sleep(1)
if event.is_directory:
return
if get_ext(event.src_path) == '.bmp':
img_path = event.src_path
bmp_img = Image.open(img_path)
resized = bmp_img.resize(
(round(bmp_img.width / 2), round(bmp_img.height / 2)),
Image.NEAREST)
filename = os.path.splitext(event.src_path)[0]
resized.save(filename + '.jpg')
if __name__ in '__main__':
while 1:
event_handler = ChangeHandler()
observer = Observer()
observer.schedule(event_handler,BASEDIR,recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()