見出し画像

お前らはまだiPhoneのショートカットを使いこなせていない

こんばんは!

Twitterで話題のmimicについて持論を呟いた結果いろんなイラストレーターからブロックされましたが、僕は元気です。
フォロワーが1人しか居ないので、ぜひ皆さんフォローしてください。

https://twitter.com/dotinkasra

さて、本日はiOSの標準アプリ、「ショートカット」についての記事を書こうと思います。

こちら、iOSはもちろんmacOSでも利用できるのですが、いまいち開拓が進んでいません。まぁ実装されてから数年経っているので「ショートカット」職人によるちょい便利レシピとかは散見されますが、細かい仕様とかは調べても出てこないです。

ただ、それでも少し触ればどれほど便利かは分かるので、今回は「ショートカット」使用術を紹介します。


ショートカットを使うな

いきなりですが、ショートカットを使いこなそうと思って必死にショートカットを作ろうとしないでください。

ぶっちゃけた話、ショートカットアプリの操作性はクソです。こんなIDEで複雑な処理なんて書きたくありません。ワークフローの組み立ても面倒で、全ての処理に共通していますが入出力される値の説明が簡素過ぎます。まともなデバッガも存在せず、あのAppleですらノーコードツールはこのレベルなのかと涙が止まらないです。

ショートカットアプリを使う上で重要な処理はたった1つ、これだけです。

これはUNIXのcurlコマンドのようなもので、HTTPリクエストを投げてレスポンスを取得できます。
よく目にするTwitterの動画保存や、インスタの動画保存などのショートカットは既存のサービスのAPIへHTTPリクエストを送っているだけのものが多いですね。

APIに頼ることの何が便利なのか、それは処理の大部分を高級プログラミング言語で書けるということ、それに付随してショートカットアプリより大幅に自由度の高い処理が実装できることです。

今回はPythonのWEBフレームワークであるFlaskを用いてLAN上にAPIサーバを立ち上げてみます。

注:この記事の内容はWindowsでも行えますが、やり方は全てMacでの解説になります。めんどうなのでね、Windowsは。

簡単な作例

YouTubeのダウンロードとか、WEBページのスクレイピングとか、動画のエンコードとか、やれることは無限にあるのですが、とりあえず今回は簡単な機能を作ってみましょう。(余談ですが、例に上げた3つのうち最後の2つはショートカットアプリだけでも簡易的なものであれば実装できます。)

皆さんはイラストのアップスケール技術の筆頭「waifu2x」をご存知でしょうか。

このサイトはwaifu2xをWEBサービスとして提供しているのですが、これと同じことが出来るサーバを建てて、ショートカットから自動的に写真アプリに保存されるような処理を書いていきます。

STEP.1 Pythonのインストール

これをみてください。

STEP.2 Flaskのインストール

これをみてください。

STEP.3 ファイルを準備する

現在のディレクトリ構造を明示します。ファイルは中身が空でも良いのでとりあえず作ってください。

.
├── app.py
├── apis
│   ├── bin
|   |   └── waifu2x
|   |       ├── modelやREADME.mdなど
|   |           └── waifu2x-ncnn-vulkan ← この実行ファイルが大事です
│   └── waifu2x.py
|__ static
    └── waifu2x

解説
・app.py
メインの処理を書くpythonファイルです。
・apis
apiの処理を記述したpythonファイルを格納するフォルダです。
・apis > bin
今回のAPIサーバで使用するコマンドの実行ファイルを格納するフォルダです。
・apis > bin > waifu2x
waifu2xの実行ファイルです。以下からmac用をダウンロードしてください。(解凍した後に出てきたディレクトリをコピーして名前を変えてね。)
https://github.com/nihui/waifu2x-ncnn-vulkan/releases
・apis > bin > waifu2x > waifu2x-ncnn-vulkan
waifu2xの実行ファイル。
・apis > waifu2x.py
受け取った画像をアップスケールする処理を記述するpythonファイルです。
・static
静的ファイルを格納するフォルダです。
・static > waifu2x
waifu2xの処理を行う際に画像を保存するフォルダです。

treeコマンド

STEP.4 メインの処理を書く

apis/waifu2x.pyをメモ帳でもvimでもVSCodeでも良いので、テキストエディタで開いて、以下のように記述してください。

# 標準モジュール
import subprocess, os

class Waifu2x():
    def do(self, image: str) -> str:
        # ここに画像を保存しますよという変数
        save_dir_path = "./static/waifu2x/"

        # 実際に保存するときは「xxx(ファイル名)2x.png」という名前にします
        upscale_image = image + "2x.png"
        cmd = [
            "./apis/bin/waifu2x/waifu2x-ncnn-vulkan",
             "-i", save_dir_path + image,
             "-s", "2",
             "-o", save_dir_path + upscale_image
        ]
        result = subprocess.Popen(cmd, stdout = subprocess.PIPE)
        result.wait()

        # アップスケールが終わったら、元の画像は消してしまいましょう。資源は有限です。
        os.remove(save_dir_path + image)

        if result.returncode == 0:
            return upscale_image
        else:
            return None

取り立てて難しい内容の処理はありません。コマンドラインからwaifu2xを実行しているだけです。

次にapp.pyを開き、以下を記述します。

# 標準モジュール
import os, mimetypes

# 自作モジュール
from apis.waifu2x import Waifu2x

# 外部モジュール
from flask import Flask, make_response, request, abort
app = Flask(__name__)

# 文字化け防止
app.config['JSON_AS_ASCII'] = False

@app.route('/api/waifu2x', methods = ['POST'])
def waifu2x_api():
    # POSTしか受け付けません。
    if not (request.method == 'POST' and 'file' in request.files):
        # エラーコードは調べると例が出てきます。400は「サーバは悪くない」ときのエラーです。
        return abort(400, 'Message: File not found.')

    file = request.files['file']
    if file.filename == '' or not file:
        return abort(400, 'Message: File not found.')

    # ショートカットから受け取る画像はここに保存されます。
    upload_folder = './static/waifu2x/'
    file.save(os.path.join(upload_folder, file.filename))

    # waifu2xのインスタンスを作り、ショートカットから受け取った画像を高画質化します。
    waifu2x = Waifu2x()
    result = waifu2x.do(file.filename)
    if result is None:
        # 高画質化するときになんか失敗したら500エラーを返します。これは「サーバが悪い」ですよというエラーです。
        return abord(500, 'INTERNAL SERVER ERROR.')

    # resultはファイル名が格納されているので、例えば「erogazo1.png」をiPhoneから送信した場合
    # upload_folder + result の中身は「'./static/waifu2x/' + 'erogazo1x2.png'」です。
    upscale_file_path = upload_folder + result

    response = make_response()
    response.data = open(upscale_file_path, "rb").read()
    response.headers['Content-Disposition'] = 'attachment; filename=' + result
    response.mimetype = mimetypes.guess_type(result)[0]

    # 資源は有限なので消します。
    os.remove(upscale_file_path)
    
    return response

if __name__ == '__main__':
    app.debug=True
    # こちらのポートはご自由にしていただいて構いません。
    app.run(host = '0.0.0.0', port = '5000', debug = True)

ここまで記述したら、以下のコマンドでサーバを立ち上げます。

$ python app.py

flaskが起動できたら成功です。
試しに、現段階で正しくアップスケールを行う処理が動作するのかを確認してみます。みなさんは面倒だと思うのでやらなくてもいいですよ。

Postmanというアプリを使い、flaskでルーティングした「localhost:xxxx(ポート番号)/api/waifu2x」へPOSTメソッドでアクセスしています。画像は適当なイラストですが、しっかりとアップスケールされて返却されているのがわかりますね。

WEBサーバ側はひとまずこれで終了です。Flaskは閉じないでください。

STEP.5 ショートカットを作る

ようやく「ショートカット」を触ります。とは言っても、処理の殆どはWEBサーバで実装しているので、単純に画像を送って結果を保存するだけで構いません。

全体像はこんな感じ。非常にシンプルですね。
一つ一つの内容を見てみましょう。

まずは共有シートに表示されるように設定し、受け取るデータの種類を「イメージ」に指定します。

続いては「URLの内容を取得」アクションで、URLには先ほど丹精込めて作ったWEBサーバを起動しているマシンのローカルIPアドレスに「:(コロン)XXXX(ポート番号)」と続けて入力します。そしてそのままURLを付け加えればOKです。まとめると「http://192.168.XX.XX:YYYY/api/waifu2x」といった具合ですね。

送信のメソッドは「POST」とします。ヘッダには特に何かを指定する必要はありません。

「本文を要求」の部分は「フォーム」を選択します。するとどんなデータを送るのかを設定できるので、キーの名前は「file」(ここ間違えたら動きません。注意)、種類を「ファイル」にし、値は共有シートから受け取った画像をそのまま送信したいので「ショートカットの入力」にします。

最後にアップスケールされた画像が格納されている「URLの内容」を写真に保存すれば完了となります。デモ動画でも貼ろうと思ったのですがモザイク処理が面倒なのでやめました。ぜひ自分の目で確かめてください。

終わり

今回は簡単なデモでしたが、WEBサーバさえ作ってしまえば「ショートカット」アプリで出来ることがかなり増えます。手間はかかりますし意味もないですが、超簡素なTwitterクライアントだって作れます。

言うまでもありませんが、当然WEBサーバはLAN内からしかアクセスできないので、家の外を出るとショートカットも使えません

かと言ってこのまま外部に公開するのはあまりにもセキュリティリスクが高いのでやめてください。HTTPS対応までは簡単に出来るとしても、ショートカットのためだけにユーザ認証まで実装するのはバカです。オススメの方法は自宅でVPNサーバを立ち上げて、iPhoneをLAN内にルーティングする方法です。WireGuardなら簡単に実装できますよ。

というわけで、みなさんもぜひ快適な「ショートカット」生活を謳歌してください。

いいなと思ったら応援しよう!