LifeWrite

気が向いたら書きます

グラブルのリアルタイムtwitter救援取得アプリを作ってみた3

下の続き

abot-s.hatenablog.com

 

アプリ

gbf-relief

 

dialogPolyfillがfirefoxで動かないのでdialog廃止

 

改修内容

・石マルチ、マグナHL追加

・謎のチャット機能追加(メンテナンス告知用)

・UIちょっと変更

 

今後の改修

・音声通知

グラブルっぽさを出す

スマホでも使えるように

グラブルのリアルタイムtwitter救援取得アプリを作ってみた2

前回から機能を改修しました。

abot-s.hatenablog.com

 

アプリ

gbf-relief

 

改修

・デスクトップ通知が出来るように

下記の通知をONにすると

下記の感じでデスクトップ通知がきます。

※この通知をコピーしてもクリップボードにIDがコピーされます。(結構重要)

・黒麒麟、黄龍、オーディンアポロン追加

(古戦場が光なので・・・)

 

今後の改修

・音声通知

グラブルっぽさを出す

・救援の種類の追加

スマホでも使えるように

グラブルのリアルタイムtwitter救援取得アプリを作ってみた1

 

gbf-raiderが閉鎖したので作ってみました。

 

※面倒なので携帯で見るの考慮してません。

dockerとかreactの練習と、nodejsのsocket使ってます。

 

下記URL

gbf-relief

 

テストなので四大天司のみです。

過去の物は出てきません、あくまでリアルタイムで取得します。

最初は何もないですが、とりあえず待ってればいつか救援出てきます。

 

gyazo.com

 

 

今後の予定(gbf-raiderにあった機能)

・デスクトップ通知したい

・音声で伝えたい

・見たい救援IDの選択

・もうちょっとグラブル感出したい

 

※サーバーのスペックがごみなので、負荷かけると多分すぐ落ちます。落ちたら増強考えます。(むしろ面白そうなので常識的な範囲で負荷かけて下さい)

 

 

 

docker(docker-compose)で10分でnginx+mysql+php-fpmの環境を作る

docker-composeを使ったら非常に簡単にWEBの環境が作れました

ホスト環境

centos7

docker-compose、dcokerインストール

yum update
#dockerインストール
curl -sSL https://get.docker.com/ | sh

#docker-composeインストール
curl -L https://github.com/docker/compose/releases/download/1.11.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

ファイル構造(最終的にdata/php/publicをweb上に公開します)

# tree
.
├── containers
│   ├── mysql
│   │   └── Dockerfile
│   ├── nginx
│   │   ├── conf
│   │   │   └── default.conf
│   │   └── Dockerfile
│   └── php-fpm
│       ├── conf
│       │   └── php.ini
│       └── Dockerfile
│   
├── data
│   ├── mariadb
│   └── php
│        └── public
└── docker-compose.yml

各ファイル

docker-compose.yml

#docker-compose.yml
version: "2"

services:
  php:
    build: ./containers/php-fpm
    volumes:
      - "./data/php:/var/www/html"
    environment:
      DB_HOST: db
      DB_PORT: 5432
  nginx:
    build: ./containers/nginx
    ports:
      - 80:80
      - 443:443
    links:
      - php
  mysql:
    build: ./containers/mysql
    volumes:
      - "./data/mariadb:/var/lib/mysql"
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_PASSWORD: ryouzi01
      MYSQL_USER: abot
      MYSQL_DB: abot
    expose:
     - "3306"

php Dockerfile

FROM php:fpm
WORKDIR /var/www/html
RUN apt-get update && docker-php-ext-install mbstring mysqli pdo_mysql
ADD conf/php.ini /usr/local/etc/php/conf.d

nginx Dockerfile

FROM nginx:1.9.12
ADD conf/default.conf /etc/nginx/conf.d

mysql Dockerfile

FROM mariadb:10.1.18

VOLUME /etc/localtime:/etc/localtime:ro

RUN sed -i -e "s/^#bind-addresss*=s*0.0.0.0/bind-address=0.0.0.0/" /etc/mysql/my.cnf

nginx.conf

server {
    listen       80 default;
    server_name  localhost;
    charset utf-8;
    root /var/www/html;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    #rewrite ^(.+)/$ $1;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        root /var/www/html/public;
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        #fastcgi_split_path_info ^(.+\.php)(.+)$;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

php.conf

#以下php.iniで使うものを設定
date.timezone = Asia/Tokyo

上記のファイルが全部そろったらdockerコンテナ起動

#docker-compose.ymlがある階層で
docker-compose build
docker-compose up -d

docker-compose stop

以上、非常に簡単に環境が作れて、かつ再構築も簡単

グラブルのwiki救援を自動取得するアプリを作った(四大天司対応)

ーーーーーーーーーーーーーーーーーーーーーーーーーー

2017/06/05

機能はこっちに引き継ぎました。

abot-s.hatenablog.com

ーーーーーーーーーーーーーーーーーーーーーーーーーー

 

以下概要

 

f:id:abot_s:20160901100400p:plain

 

上記の様な簡単アプリで自動で裏でwikiの内容を取得してるだけ。

様はヤフーのtwitterリアルタイム検索のwiki版で、自動的にクリップボードに救援IDを設定する。

リアルタイム検索はtwitterAPI使ってると思うけどこっちは力技です。

WebBrowserコントロールを使っています。

 

仕組みは以下の通り

グラブルのマルチ(グランデ、よわバハ)の救援ページ

通常マルチバトル救援募集板 - グランブルーファンタジー(グラブル)攻略wiki

 

・ページの仕様

1.上記の内容は全部inputタグで囲まれている

2.救援のINPUTタグにはclass="pcmt"が設定されている(救援以外もあるが)

3.各救援は昇順

 

・アプリ仕様

1.HtmlElementCollectionにINPUTタグを検索して格納、

そのINPUTのclassタグにpcmtが設定されていればそれを救援と認識

それを行ごとに画面出力

2.最新の行から\s[0-9a-z]{8}で救援IDを取得してクリップボードに張り付ける。(簡単に貼り付けで救援に入れるように)

3.取得間隔は10秒

4.新しい救援取得時にWindowsのNotifyIconを使いタスクトレイに通知

 

・問題

1.サイトのURLが変更されたらいちいち更新が面倒

2.サイトの表示が変わったらおしまい

3.使う人の書き方が変わったらおしまい

 

・利点

これを使ってからグランデに入れなかったことありません(キリッ

 

・・・としょうもないものを作ってしまった。

iphoneでの自動取得のほうが結構需要ありそうだけど、都度のクロールのせいで通信食いそう。

直接アプリに救援ID張り付けようと思ったけど、さすがにアプリに介入するといろいろまずそうなのでここまで。ほしい人いるだろうか・・・。

 

・追記

dropboxに上げてみました。
取得間隔は10秒固定です。(wikiに負荷をかけてほしくないため)

グランデ、よわバハのページだけです。要望があれば作るかもしれませんが・・・

使用は自己責任でお願いします。いかなる不利益を被っても責任は負いかねます。

サイトの構造が変わったりしてidが取得できなくなった場合、連絡いただければ調べる

(かも)しれませんが期待しないでください。

Dropbox - グラブルwiki救援自動クリップボード貼り付けアプリ.zip

 

・追記2 09/14 12:02

1.正規表現でのID抽出部分を変更(全角でID書く人とか居たのと、先頭にID書いた場合に抽出できなかったので)

2.初回起動時、IDが見つからない場合は通知しないように修正

 

・追記3 09/21 18:57

特定環境において起動時にエラーが出ていたので、dllも付属するように変更。

どのようなDLLかは↓の方の記事を参照

mshtml.dllの配布に関するメモ - wave1008の日記

 

追記4 2017/03/18

なんかアクセスめっちゃ増えていて、また開発環境も手元に戻ってきたので改良してます。(自分も使いたいので)

多分四大天司の影響だと思いますが・・・。

ちなみに引退してましたがバハ引いたので復帰しました。( ・´ー・`)

こんな感じになると思います。

f:id:abot_s:20170318195841p:plain

 

・抽出ワードは「ウリエル」と入れた場合にサイトに下記の行があったとき「ウリエル」の行だけ認識するものです。(表示自体は全行出します)

ウリエル 416500C6

ラファエル e6c1c3d0

・保存は次回起動時に覚えておくため用です。radioボタンを変えてもすぐに反映されます。 

 

※通常マルチも要望がありましたがあまり更新されないので、twitterで検索したほうがいいと思います。

また、コメントにスマホのアプリとありましたがそれ用に作っていないので多分無理だと思います。最近twitterでどなたかが作っているアプリ見かけた気がするけど・・・。

 

追記5 2017/03/19 四大天司対応させました。

https://www.axfc.net/u/3788133

パスワードは wiki です。

ソース残ってなかったから新規に作り直したので、不具合出たらご連絡ください。

意見もお待ちしております。

グラブルのフレンドも募集中です ID:6372142 

 

..etc 

いまの10秒更新だとほぼ無理ですね・・・。

3秒更新だとID入力した後の表示人数1/30人で入ることが3連続で出来たので、落ち着いたら配布します。

6人HELLは上手くいってないです。

 

 

 

 

 

 

意外に簡単?swiftで動的にカメラ内の顔を検出して処理する(CIDetector)

最近流行りのVRもいいけど、現実が全部嫁になればいいのにと日々思っているこの頃

そんな幻想を抱きながら顔を置き換える処理を、Swiftで簡単に顔検出が出来るみたいなのでやってみました

class TestViewController: UIViewController,UIGestureRecognizerDelegate,AVCaptureVideoDataOutputSampleBufferDelegate  {
    let captureSession = AVCaptureSession()
    let videoDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
    let audioDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)
    //let fileOutput = AVCaptureMovieFileOutput()
    var videoOutput = AVCaptureVideoDataOutput()
    var hideView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //各デバイスの登録(audioは実際いらない)
        do {
            let videoInput = try AVCaptureDeviceInput(device: self.videoDevice) as AVCaptureDeviceInput
            self.captureSession.addInput(videoInput)
        } catch let error as NSError {
            print(error)
        }
        do {
            let audioInput = try AVCaptureDeviceInput(device: self.audioDevice) as AVCaptureInput
            self.captureSession.addInput(audioInput)
        } catch let error as NSError {
            print(error)
        }
        
        self.videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey : Int(kCVPixelFormatType_32BGRA)]
        
        //フレーム毎に呼び出すデリゲート登録
        let queue:dispatch_queue_t = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
        self.videoOutput.setSampleBufferDelegate(self, queue: queue)
        self.videoOutput.alwaysDiscardsLateVideoFrames = true
        
        self.captureSession.addOutput(self.videoOutput)
        
        let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession) 
        videoLayer.frame = self.view.bounds
        videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        
        self.view.layer.addSublayer(videoLayer)
        
        //カメラ向き
        for connection in self.videoOutput.connections {
            if let conn = connection as? AVCaptureConnection {
                if conn.supportsVideoOrientation {
                    conn.videoOrientation = AVCaptureVideoOrientation.Portrait
                }
            }
        }
        hideView = UIView(frame: self.view.bounds)
        self.view.addSubview(hideView)
        self.captureSession.startRunning()    
    }
    func imageFromSampleBuffer(sampleBuffer: CMSampleBufferRef) -> UIImage {
   //バッファーをUIImageに変換
        let imageBuffer: CVImageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)!
        CVPixelBufferLockBaseAddress(imageBuffer, 0)
        let baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)
        let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
        let width = CVPixelBufferGetWidth(imageBuffer)
        let height = CVPixelBufferGetHeight(imageBuffer)
        
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = (CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue)
        let context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo)
        let imageRef = CGBitmapContextCreateImage(context)
        
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0)
        let resultImage: UIImage = UIImage(CGImage: imageRef!)
        return resultImage
    }
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!)
    {
        //同期処理(非同期処理ではキューが溜まりすぎて画像がついていかない)
        dispatch_sync(dispatch_get_main_queue(), {
            
            //バッファーをUIImageに変換
            var image = self.imageFromSampleBuffer(sampleBuffer)
            let ciimage:CIImage! = CIImage(image: image)
            
            //CIDetectorAccuracyHighだと高精度(使った感じは遠距離による判定の精度)だが処理が遅くなる
            var detector : CIDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options:[CIDetectorAccuracy: CIDetectorAccuracyLow] )
            var faces : NSArray = detector.featuresInImage(ciimage)

            // 検出された顔データを処理
            for subview:UIView in self.hideView.subviews  {
                subview.removeFromSuperview()
            }
            
            var feature : CIFaceFeature = CIFaceFeature()
            for feature in faces {
                
                // 座標変換
                var faceRect : CGRect = feature.bounds
                var widthPer = (self.view.bounds.width/image.size.width)
                var heightPer = (self.view.bounds.height/image.size.height)
                
                // UIKitは左上に原点があるが、CoreImageは左下に原点があるので揃える
                faceRect.origin.y = image.size.height - faceRect.origin.y - faceRect.size.height
                
                //倍率変換
                faceRect.origin.x = faceRect.origin.x * widthPer
                faceRect.origin.y = faceRect.origin.y * heightPer
                faceRect.size.width = faceRect.size.width * widthPer
                faceRect.size.height = faceRect.size.height * heightPer
                
                // 顔を隠す画像を表示
                let hideImage = UIImageView(image:UIImage(named:"trump.jpg"))
                hideImage.frame = faceRect
                
                self.hideView.addSubview(hideImage)
            }
        })
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}


左が参考オバマ、右が処理後

感想

・今の状態だとiphone越しなのでVRみたいにステレオマッチングしたら面白そう、ゴーグルかけて隠す画像も3Dモデルにして、顔の向きに応じて3Dモデルも回転したら・・・

・実際上書きしたところを想像すると、もしストローとかポッキーを相手が咥えていたら刺さりそう

・正面画像の検出精度はLowでもなかなかだが、横顔はかなり厳しい印象

・細かい精度を求めないのであれば、かなり簡単なので結構応用出来そう

・CIDetectorではなくOpenCVだと顔以外も検出できるらしい


参考

swiftでAVCaptureVideoDataOutputを使ったカメラのテンプレート - Qiita

100均Webカメラ2台でステレオマッチングやってみた - 銀の弾丸

Swiftで笑顔認識をやってみた - Qiita

twilioを使って友達がいるアピールをしよう!

twilioについては↓

Twilio for KDDI Web Communications | クラウド電話API

 

これを使い自分に電話を掛け、あたかも友達から電話が掛ってきたように見せかける。

 

まず契約だが、twilioには1番号につき月108円(友達料)が必要だ。

缶ジュース1本で友達が出来るなら安いだろう。

取得した番号を携帯の連絡先に登録することも忘れるなよ!

 

番号を取得した後 AccountSid と AuthToken が貰えるので控えておこう。

 

今回はPHPを利用する。

 

1.自分のサーバー上に↓のライブラリを入れる。(サーバーない人はAWSやらHerokuやらgoogle cloud platformやらで作っておこう)

twilio/twilio-php · GitHub

 

2.ライブラリを呼び出し電話をかけるphpを書く

<?

require('/path/to/twilio-php/Services/Twilio.php');

$account_sid = '[取得したAccountSid]';

$auth_token = '[取得したAuthToken]';

$client = new Services_Twilio($account_sid, $auth_token);

$call = $client->account->calls->create(
  '[取得した電話番号]', ※日本だと+81変換必要
  '[自分の携帯の電話番号]'※日本だと+81変換必要
  '[xmlのurl(3で作る)]' ,
  array(
    'Method' => 'GET',
    'FallbackMethod' => 'GET',
    'StatusCallbackMethod' => 'GET',
    'Record' => 'false'
  ) 
);
echo $call->sid;

?>

 

3.xmlファイルを作りに電話でなにをするか書く。

<?xml version="1.0" encoding="UTF-8"?>

<Response>
<Say  language="ja-jp">はじめまして</Say>
</Response>

 

↑の例では機械音声を流しているだけだが、女の子が入っている音声ファイルを流せば彼女と会話しているようにも出来るぞ!

 

4.自分で2のphpにアクセスする、またはクーロンとかで自動で指定時間にphpを実行するようにする。

※自分でアクセスする場合はセキュリティに気をつけろ!不正利用されて高額請求が来ても知りません。

 

5.自分の携帯に電話がかかってくるので、友達(録音)と話すだけ!

 

これで友達がいるアピールがいつでもできる!やったね!

 

・・・。