Corredor

ウェブ、プログラミングの勉強メモ。

iPhone 11 に搭載された超広角レンズを使って AVFoundation で撮影する方法

環境情報

  • Xcode v11.0 (11A420a)
  • iPhone 11 Pro Max : iOS 13.0
  • Swift 4 プロジェクト

実装方法

ベースとなる AVFoundation のコードは以下のプロジェクトなどを参照。

github.com

以下は動画撮影の例だが、コレまで以下のようにカメラを起動していたところがあるとする。

videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)

この部分を以下のように直せば、超広角レンズが使える時に超広角レンズを採用して撮影できる。

if #available(iOS 13.0, *) {
  videoDevice = AVCaptureDevice.default(.builtInUltraWideCamera, for: .video, position: .back)
  print("超広角レンズを使用")
} else {
  videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
  print("通常のレンズを使用")
}

簡単!

また、AVCaptureMultiCamSession を使って、複数のカメラを使って同時に動画を録画するプロジェクトは、以下の記事で紹介している。コチラも併せてドウゾ。

neos21.hatenablog.com

github.com

iPhone SDK Application Development: Building Applications for the AppStore

iPhone SDK Application Development: Building Applications for the AppStore

Twitter に投稿された画像・動画をダウンロードする CLI ツール「twsv」を作った

Twitter に投稿された画像や動画をダウンロードする際、

としていた。

コレをもっと手軽に、複数のツイートからのダウンロードをバッチ処理できないかと思い、自分で同様のツールを作ってみることにした。その名も twsv。「TWitter SaVer」の略だ。

先に作ったツールの紹介

twsv は Node.js 製の CLI ツール。npm でインストールできる。パッケージ名としては @neos21/twsv

$ npm install -g @neos21/twsv

裏では Twitter API を使っているので、各自 Twitter の Developer 登録を行い、API キーを発行しておくこと。環境変数で以下のように設定しておく。

# Twitter クレデンシャル情報を設定する
export TWITTER_CONSUMER_KEY='Your Consumer Key'
export TWITTER_CONSUMER_SECRET='Your Consumer Secret'
export TWITTER_ACCESS_TOKEN_KEY='Your Access Token Key'
export TWITTER_ACCESS_TOKEN_SECRET='Your Access Token Secret'

グローバルインストールしたら twsv コマンドが使えるようになっているので、以下のように Twitter の URL を引数で指定してやる。

# 指定ユーザのタイムラインより直近200件のツイートを取得し、それらに紐付く画像・動画を取得する
$ twsv https://twitter.com/USERNAME

# 指定のユーザのいいね一覧より直近200件のツイートを取得し、それらに紐付く画像・動画を取得する
$ twsv https://twitter.com/USERNAME/likes

# 指定のツイートから画像・動画を取得する
$ twsv https://twitter.com/USERNAME/status/0000000000000000000

デフォルトの保存先は、コマンドを実行した時のカレントディレクトリに twsv-downloads/ ディレクトリを作り、その下にファイルを保存する。

保存先ディレクトリを変更する場合は、環境変数か第2引数で指定できる。両方指定されている場合は第2引数が優先される。

# 環境変数で指定して実行
$ export TWSV_SAVE_DIRECTORY='/home/downloads'
$ twsv https://twitter.com/USERNAME/status/0000000000000000000

# 第2引数で指定して実行
$ twsv https://twitter.com/USERNAME/status/0000000000000000000 '/home/downloads'

動画については、画質 (ビットレート) が一番良いモノを選んで取得している。

以降ツールを作るまでの苦労話

Syncer のツールはブラウザで操作し、「URL 貼り付け」→「動画 URL を選んで DL」というステップを踏んでいた。複数のツイートから動画を拾いたい場合は毎回この操作でダルかった。

そこで、ツールは CLI ツールにし、ツイートの URL をかき集めたらバッチ処理で一気に DL できるようにすることにした。

当初は Twitter API を使わないで済む方法がないかと思い、Web ページをスクレイピングして取得できないか調べてみた。

画像については、ページ内の img 要素を取得し、https://pbs.twimg.com/media/ を含む URL を引っ張ってくればダウンロードできた。

動画については、video 要素を調べてみたが、blob: から始まる URL になっていて、うまくダウンロードできなかった。Syncer では https://video.twimg.com/ext_tw_video/【色々…】.mp4 といった URL が拾えていたのだが、ウェブページ上からはこの URL は拾えなさそうだったので断念した。

仕方なく Twitter API を使ってツイートオブジェクトを取得し、画像や動画の URL を拾い上げることにした。

Twitter API を叩くのは、公式の twitter npm パッケージを使った。いいね一覧とユーザタイムラインを拾えそうだったので、ツイート単体の DL だけでなく、そういうツイート一覧から拾えるだけ画像や動画を拾えるようにも対応させてみた。

ツイートオブジェクトの中は愚直に見ていった。画像も動画も、ツイートオブジェクトの .extended_entries.media という配列プロパティの中に入っている。画像の場合は、配列の中の各要素の .media_url が目的の URL。

動画の場合は .video_info.variants プロパティが配列になっていて、ビットレートごとに目的の URL が入っている。そこで、ビットレートを比較して、ビットレートが一番大きい動画の URL を拾うことにした。

動画のダウンロードには request-promise を使ったが、同時接続数が多いと上手くダウンロードできなくなってしまったので、同時接続数を制限する仕組みを入れた。以下の記事のコードほぼそのまま。

保存先ディレクトリの存在確認、作成、保存処理などは fspath あたりの Node.js 組み込みのパッケージを使っている。

最終的に、外部依存パッケージは twitterrequestrequest-promise の3つで、コードは単一の JS ファイルに454行でまとまった。

以上

割と愚直にコーディングしまくっただけだが、自分が欲しいモノが作れた。Twitter API を使っているので、API コールのレート制限が確認できるとなお良しか。コンソール出力はもう少し改善の余地ありそうだが、特に気にせず。w

LINE, Instagram, Facebook, Twitter やりたいことが全部わかる本 この一冊で今すぐはじめられる

LINE, Instagram, Facebook, Twitter やりたいことが全部わかる本 この一冊で今すぐはじめられる

Twitter Perfect GuideBook 改訂版

Twitter Perfect GuideBook 改訂版

git pull 時に --set-upstream-to とか言われるのを回避するコマンドを作る

git pull した時に --set-upstream-to しろみたいなコメントが出て、git pull 出来ない時がある。

$ git pull

There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details

    git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=origin/<branch> branch_name

恐らく以下の記事で触れた、git push 時の --set-upstream オプションを省くための作業を行ったことで、この問題が起こるようになったっぽい。

neos21.hatenablog.com

ローカルで新規ブランチを作り、初めて git push した直後に git pull すると、「どこのリモートブランチと紐付いてるのか分からん」と言われてしまうっぽい。

対処法としては、指示されているとおりの git branch --set-upstream-to コマンドを叩けば良いのだが、いちいちカレントブランチ名を入力しないといけないのが面倒臭い。

ということで、gplf (git pull force) というコマンドを作ってみた。git push --force があるんだから、git pull --force (とにかく Pull させろ) というコマンドがあってもいいじゃろ、ということで命名。w

#!/bin/bash

# git pull 時に --set-upstream-to しろというエラーが出た時に自動処理させる

# カレントブランチ名
current_branch_name= git rev-parse --abbrev-ref @

# リモートブランチを指定して git pull する
git branch --set-upstream-to="origin/${current_branch_name}" "${current_branch_name}"
git pull

このスクリプトを gplf という名前で保存し、PATh が通っているところに配置、$ chmod +x ./gplf と実行権限を付与すれば準備 OK。

僕は git pullgpl とエイリアス登録しているので、gpl と入力して前述のエラーが出たら、gplf と打ち直してこのコマンドを実行させる、というワケ。

コレでもう --set-upstream-to エラーに対して手間をかけなくて済むぞい。

わかばちゃんと学ぶ Git使い方入門〈GitHub、Bitbucket、SourceTree〉

わかばちゃんと学ぶ Git使い方入門〈GitHub、Bitbucket、SourceTree〉

負荷試験のために Locust を使ってみる

以前、負荷テストに JMeter を使ったことがあった。GUI で設定・監視でき、使用感自体はそこまで悪くなかった。

neos21.hatenablog.com

今回、また負荷テストをやることになり、コマンドラインでサクッと設定できるようなモノはないのかなーと思って調べてみたところ、Locust というツールを見つけたので紹介する。

インストール

Locust は Python 製のツールなので、pip を使ってインストールする。MacOS と CentOS で試してみたのでそれぞれやり方を書いておく。

MacOS Mojave の場合

MacOS の場合は、先に Homebrew を使って python3 と、Locust が使う libev という依存ツールをインストールしておく必要がある。

$ brew install python3 libenv

Python3 の PATH 設定とかは説明を省略。以下のコマンドで Locust をインストールする。

$ python3 -m pip install locustio

インストールできたら、以下のように locust コマンドが使えるようになっているか確認しよう。

$ locust --version
[2019-07-03 14:36:58,742] mac-mbp.local/INFO/stdout: Locust 0.11.0

Linux CentOS 7 の場合

CentOS の場合は Yum で Python および pip を用意する。

$ sudo yum install python-pip
$ sudo pip install pip --upgrade

そしたら Locust をインストール。

$ sudo pip install locustio

…すると、requests というモジュール関連のエラーが出てしまった。調べたら以下のように --ignore-installed オプションで回避できるみたいなのでやり直す。

$ sudo pip install locustio --ignore-installed requests

コレで $ locust コマンドが使えるようになった。

タスクを定義する

Locust が使えるようになったら、実行したいタスクを定義する。「どこにアクセスして何をする」といった処理を「タスク」として定義するのだが、Python で実装する。

ファイル名は locustfile.py という名前がデフォルトだ。試しに以下のように実装する。

# -*- coding: utf-8 -*-

# Python2 の場合は以下2行をアンコメントして入れておく
# from __future__ import absolute_import
# from __future__ import unicode_literals

from locust import HttpLocust, TaskSet, task

# クラス名は任意の名前
class UserTaskSet(TaskSet):
  # 数字は実行比率を表す。例えば @task(2) は @task(1) の倍の数だけ実行される
  @task(1)
  def index(self):
    # トップページにアクセスするだけ
    self.client.get("/")

# コレもクラス名は任意
class WebsiteUser(HttpLocust):
  task_set = UserTaskSet
  # タスク実行の最短待ち時間 (ミリ秒)
  min_wait = 100
  # タスク実行の最大待ち時間 (ミリ秒)
  max_wait = 1000
  # それぞれのタスクの実行間隔は min_wait と max_wait の間のランダム値になる

こんな感じ。

接続するホスト名はまだ指定していないが、どこかのホストのルート / に GET リクエストを投げるだけの簡単なスクリプトだ。

テスト対象のサーバを用意する

テスト対象のサーバを用意しておこう。後で URL 指定するので、既にどこぞに立ててあるサイトを IP アドレスやドメインで指定しても良いし、localhost でも良い。

今回は http://localhost:3000/ でアクセスできるサーバを立ててあるテイとする。

Locust を起動する

locustfile.py ファイルを用意したら、そのファイルがあるディレクトリで以下のようにコマンドを実行する。

# 末尾にスラッシュを付けないこと (付けるとスラッシュが2つ付いてしまう)
$ locust -H http://localhost:3000

[2019-07-22 13:34:10,170] NeosMacBook.local/INFO/locust.main: Starting web monitor at *:8089
[2019-07-22 13:34:10,171] NeosMacBook.local/INFO/locust.main: Starting Locust 0.11.0

http://localhost:3000 にリクエストを投げるテストを始めるよー、ということで -H (--host) オプションを指定している。

コマンドを実行すると、ターミナルは起動したままになる。テストの開始や設定は、Locust が起動した簡易サーバで行う。デフォルトでは

  • http://localhost:8089/

にブラウザでアクセスすると、Locust の画面が登場する。

f:id:neos21:20190806164642p:plain

テキストボックスが2つ表示されるが、意味は以下のとおり。

  • Number of users to simulate : 最大でいくつの同時アクセス数にするか (コレを「ユーザ数」と表現)
  • Hatch rate (users spawned/second) : 1秒間で何ユーザ増やしていくか (上のユーザ数に達するまでの期間となる)

今回は、最大で同時に10アクセスさせるようにし、1秒に1ユーザずつ増やしていこうと思うので、上の欄に 10・下の欄に 1 と入力し、「Start swarming」ボタンを押すと、テストが始まる。

Locust の Web UI はとても見やすいので、特に迷うこともないと思う。累計リクエスト数、エラーレスポンスの数、レスポンスまでの時間などの情報が表示される。気が済んだら右上の方にある「Stop」ボタンを押せば処理が止まる。

停止後、「New test」ボタンを押下すると再度先程のテキストボックスが表示されるので、今度はさらにリクエスト数を多くしたりして、テストが再開できる。

テストを終えたくなったら、起動しっぱなしのターミナルに戻って Ctrl + C を押下すれば良い。

CUI のみでテストを行う

MacOS でテストする場合は、このようにターミナルとブラウザを行き来すればテストできるが、CUI のみの CentOS などでテストする場合は、GUI ブラウザが開けない。そこで、locust コマンドのオプション引数を使ってテストを設定してやる。

$ locust -H http://localhost:3000 --no-web -c 10 -r 1
  • --no-web : CUI モードにする場合は必須
  • -c (--clients) : 「Number of users to simulate」に同じ
  • -r (--hatch-rate) : 「Hatch rate」に同じ

このように、テキストボックスに入力していた情報を -c-r オプションで渡してやると、その場でテストが開始する。中止する場合は Ctrl + C で、最後にまとまったレポートがコンソール出力される。

大量リクエストで HTTPConnectionPool 関連のエラーが出る場合は…

少ないユーザ数でテストしている場合は正常なのに、ユーザ数を多くすると HTTPConnectionPool 関連のエラーで Fail となる場合は、Locust を実行する環境の「ファイルディスクリプタ」の上限に達していることが原因。

  • 例えばこんなエラーが出る時。
ConnectionError(MaxRetryError("HTTPConnectionPool(host='localhost', port=3000): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x....>: Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known'))"))

ConnectionError(MaxRetryError("HTTPConnectionPool(host='localhost', port=3000): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x....>: Failed to establish a new connection: [Errno 24] Too many open files'))"))

ConnectionError(ProtocolError('Connection aborted.', ConnectionResetError(54, 'Connection reset by peer')))

本来はどれだけファイルが開けるか、という上限設定なのだが、ソケット通信でも使われるので、この項目が影響している。

MacOS の場合、上限値は以下のように確認できる。

$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
file size               (blocks, -f) unlimited
max locked memory       (kbytes, -l) unlimited
max memory size         (kbytes, -m) unlimited
open files                      (-n) 256        # ← ココ
pipe size            (512 bytes, -p) 1
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1418
virtual memory          (kbytes, -v) unlimited

現在の上限値は 256 なので、以下のように増やしてやろう。

$ ulimit -S -n 2048

再度 $ ulimit -a で確認すれば、open files の値が上昇していることが分かるだろう。

この効果はそのターミナル (セッション) のみなので、Locust を実行するターミナルタブで設定してやること。

POST 送信する時は

先程の locustfile.py の例は、単純な GET 通信だったが、POST リクエストを投げたい場合はどうするかというと、こうする。

class UserTaskSet(TaskSet):
  @task(1)
  def index(self):
    params = {
      "data": [
        { "userId": "u001", "name": "Jane Doe" },
        { "userId": "u002", "name": "John Smith" }
      ]
    }
    res = self.client.post("/register", json=params)

こんな風に POST 送信する JSON を組み立てて、self.client.get() ではなく self.client.post() で投げれば良い。

以上

Locust の使い方を簡単ではあったが紹介した。

BASIC 認証を通り抜けて次の画面である操作をして…というような複雑なタスクも定義できるので、使い方次第でかなり細かな負荷テストができるだろう。

[rakuten:booxstore:12025967:detail]

Amazon Web Services負荷試験入門―クラウドの性能の引き出し方がわかる (Software Design plusシリーズ)

Amazon Web Services負荷試験入門―クラウドの性能の引き出し方がわかる (Software Design plusシリーズ)

初めての自動テスト ―Webシステムのための自動テスト基礎

初めての自動テスト ―Webシステムのための自動テスト基礎