Misskeyメディアプロキシサーバを作ってみた
これは Misskey Advent Calendar 2021 12日目の記事です。
Misskeyはメディアプロキシをサポートしており、画像の圧縮やキャッシュもできます。
画像がよく流れるタイムラインを外で見るとモバイルデータをそこそこ食います。
そこで、画像を超圧縮するプロキシサーバーをPythonで作ってみることにしました。
Webサーバーフレームワークとして、Flaskを使用しています。
全体のコードは記事の最後に書きました。
仕様
Misskeyのメディアプロキシの設定は、.ymlファイルにあります。
mediaProxy: https://host-to.proxy
このようにアドレスを設定すると、https://host-to.proxy/proxy?url=[URL]
という形で使われます。
サーバーでは /proxy
へのリクエストを受け付けるようにします。
仕組み
プロキシしたいURLは、クエリとしてurl
に指定されます。
これをサーバーが取得して、JPEGで圧縮して返してやります。
Pythonで書くなら、本当であればPillowを使うべきですが、慣れているFFmpegを使いました。
サーバーとして使う環境にFFmpegがインストールされている前提で進めていきます。
FFmpegのオプション
圧縮する際のオプションはこんな感じです。
ffmpeg -hide_banner -http_proxy PROXY_URL -i URL -vcodec mjpeg -qmax 20 -q 20 -vf "scale=360*dar:360" -f image2 -
オプション | 意味 |
---|---|
-http_proxy PROXY_URL | 指定したプロキシサーバー経由でダウンロードする |
-i URL | オリジナルURL |
-vcodec mjpeg | コーデック選択(JPEG) |
-qmax 20 | 品質最大20に限定 ※1 |
-q 20 | 品質20に指定 ※1 |
-vf "scale=360*dar:360" | 画像サイズで、縦横比を維持したまま縦幅が360pxになるようにリサイズ |
-f image2 - | 標準出力に出力 |
※1: 品質の値は、デフォルトで2が最高、96が最低。 |
実装
このパラメータをPythonで使えるように変えて、subprocessで実行します。
data = subprocess.check_output(['ffmpeg', '-hide_banner', '-http_proxy', proxy_url, '-i', url, '-vcodec', 'mjpeg', '-qmax', '20', '-q', '20', '-vf', 'scale=360*dar:360', '-f', 'image2', '-'], stderr=subprocess.DEVNULL)
標準エラー出力を/dev/nullに流しているのは、FFmpegの変換ログを抑制するためです。(本当ならログレベル-loglevel
で抑制すればよかったかも)
画像データは標準出力に流れてくるので、そのまま送信します。
return send_file(io.BytesIO(data), environ=request.environ, mimetype='image/jpeg', as_attachment=False)
試す
試しにブラウザで直接アクセスして確かめてみます。
https://host-to.proxy/proxy?url=...
まず、元画像です。自分が撮影した写真です。
解像度は4Kで、ファイルサイズは8MBもあります。
これを画像圧縮プロキシに通すと、こうなります。
解像度は640x360まで縮小され、ファイルサイズは15KBになりました。
nginxなどでキャッシュすれば、サーバー側の負担が減るかと思います。
サンプルコード
この画像圧縮プロキシサーバーのソースコードです。
Python3.8/3.9で動作確認済みです。pipでFlask
をインストールする必要があります。
from sys import stderr, stdout
from flask import Flask, request
from flask.helpers import make_response
import subprocess, io
from werkzeug.utils import send_file
app = Flask(__name__)
@app.route('/proxy')
def do_proxy():
url = request.args.get('url')
if not url:
return make_response('', 400)
try:
data = subprocess.check_output(['ffmpeg', '-hide_banner', '-http_proxy', proxy_url, '-i', url, '-vcodec', 'mjpeg', '-qmax', '20', '-q', '20', '-vf', 'scale=360*dar:360', '-f', 'image2', '-'], stderr=subprocess.DEVNULL)
except Exception as e:
return make_response('', 500)
return send_file(io.BytesIO(data), environ=request.environ, mimetype='image/jpeg', as_attachment=False)
app.run(host='127.0.0.1', port=8080, threaded=True)
さいごに
少ないコードで簡単に画像圧縮プロキシを作ることが出来ました。
クライアントがWebPに対応している場合に、サーバー側もWebPでエンコードすれば、画質を保ちながらさらにサイズ削減できるかもしれません。
Misskeyは細かいところも設定できるという強みがありますので、ぜひおひとり様インスタンスに使ってみてください。
Misskeyをはじめよう (Misskey公式サイト)
Discussion