RaspberryPiでRFID-RC522を使ってみる【失敗編】

RaspberryPiでRFID-RC522を使ってみる【失敗編】

このエントリーは失敗編です。失敗に至るメモのため、情報が基本ないので読み飛ばしてください。

以前の勉強会で教えてもらった(もう数年も前になりますが)NFCのタグシールを何かに使えないかと思って購入してみました。そのまま使うのもなんなのでArduinoやRaspberryPiでも使えないかなと思って以下も同時に購入してみました。

リーダーもこの値段で購入できるんですね。いい時代なのか、それともちょっとやそっとではお金を稼ぐことができない時代なのか。

このリーダーをネットで検索すると、こちらのリンクにヒットしました。結構Arduinoのネタは多いようなんですが、RaspberryPiはすくないようです。

webcache.googleusercontent.com

ふむふむと読んでいくと…

・the kernel to use Device Tree?
 で Noを選択します。(コレ重要!)

raspi-configコマンドで表示されるメニューで設定値が表示されるようなのですが、うちのRaspberryPiでは表示されないようです。なぜ?カーネルのバージョンなどで違いがあるのでしょうか。念のため、昔使っていたRaspberryPi2でも同様の設定は出ませんでしたし。ネットを検索してもそれっぽい情報はありませんでした。

とりあえず、とても嫌な予感がしますが、そのまま進めていくことにします。

候補としては以下の2つになると思います。そろそろC言語はつらいので…

  • SPI-Py(SPI制御ライブラリ)&MFRC522-pythonの組合せ
  • pi-rc522

今回は、候補の中で使用するのはPythonからこのリーダーを扱えるpi-rc522モジュールになります。 リンク先のページではgitでCloneしてインストールしていますが、ヌルいPython使いの自分はpipでインストールしました。

github.com

$ pip install pi-rc522
Collecting pi-rc522
  Downloading pi-rc522-2.2.1.tar.gz
Collecting RPi.GPIO (from pi-rc522)
  Downloading RPi.GPIO-0.6.3.tar.gz
Collecting spidev (from pi-rc522)
  Downloading spidev-3.2.tar.gz
Building wheels for collected packages: pi-rc522, RPi.GPIO, spidev
  Running setup.py bdist_wheel for pi-rc522 ... done
  Stored in directory: /home/pi/.cache/pip/wheels/83/45/21/0bc28b47c8dfb87d00d806b10c8ffb733e36a8b927668ce42b
  Running setup.py bdist_wheel for RPi.GPIO ... done
  Stored in directory: /home/pi/.cache/pip/wheels/ae/4d/3b/e924997dbf06810adf3b2e37f1d9627b2327eb9cbb285949c9
  Running setup.py bdist_wheel for spidev ... done
  Stored in directory: /home/pi/.cache/pip/wheels/e4/9b/5f/cf0ec030fc958b72315a15412130e4e1dc6040cdb490aa21fb
Successfully built pi-rc522 RPi.GPIO spidev
Installing collected packages: RPi.GPIO, spidev, pi-rc522
Successfully installed RPi.GPIO-0.6.3 pi-rc522-2.2.1 spidev-3.2

同時にGPIOやSPIのモジュールもインストールされるようです。GitでCloneするとサンプルコードも同時に手に入るのですが、pipインストールでは手に入らないので、Githubにいってサンプルを取得します。

【サンプルページ】 github.com

$ wget https://raw.githubusercontent.com/ondryaso/pi-rc522/master/examples/Read.py
--2017-09-18 03:08:03--  https://raw.githubusercontent.com/ondryaso/pi-rc522/master/examples/Read.py
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています... 151.101.72.133
raw.githubusercontent.com (raw.githubusercontent.com)|151.101.72.133|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 983 [text/plain]
`Read.py' に保存中

Read.py             100%[===================>]     983  --.-KB/s    in 0s

2017-09-18 03:08:03 (6.98 MB/s) - `Read.py' へ保存完了 [983/983]

RFIDのリーダーとの接続は RC522側の IRQだけ接続不要です。

RC522モジュール RaspberryPi
1 3.3V GPIO +3.3V(3V3)
2 RST GPIO GPIO25
3 GND GPIO GND(GND)
4 MISO GPIO MISO(GPIO9)
5 MOSI GPIO MOSI(GPIO10)
6 SCK GPIO SCLK(GPIO11)
7 SDA GPIO CE0(GPIO8)

以下のように実行すればうまく…

【Read.py】

#!/usr/bin/env python

import signal
import time
import sys

from pirc522 import RFID

run = True
rdr = RFID()
util = rdr.util()
util.debug = True

def end_read(signal,frame):
    global run
    print("\nCtrl+C captured, ending read.")
    run = False
    rdr.cleanup()
    sys.exit()

signal.signal(signal.SIGINT, end_read)

print("Starting")
while run:
    rdr.wait_for_tag()

    (error, data) = rdr.request()
    if not error:
        print("\nDetected: " + format(data, "02x"))

    (error, uid) = rdr.anticoll()
    if not error:
        print("Card read UID: "+str(uid[0])+","+str(uid[1])+","+str(uid[2])+","+str(uid[3]))

        print("Setting tag")
        util.set_tag(uid)
        print("\nAuthorizing")
        #util.auth(rdr.auth_a, [0x12, 0x34, 0x56, 0x78, 0x96, 0x92])
        util.auth(rdr.auth_b, [0x74, 0x00, 0x52, 0x35, 0x00, 0xFF])
        print("\nReading")
        util.read_out(4)
        print("\nDeauthorizing")
        util.deauth()

        time.sleep(1)

はずだ…

$ python Read.py
Starting

10分後…全く動かねえ…

ではということで

方法その1であった、SPI-Py(SPI制御ライブラリ)&MFRC522-pythonの組合せもやってみます。

$ git clone https://github.com/lthiery/SPI-Py.git
$ cd SPI-Py
$ sudo python setup.py install
$ cd ..
$ git clone https://github.com/mxgxw/MFRC522-python.git
$ cd MFRC522-python
$ sudo python Read.py

これでも動作せず。/(^o^)\オワタ。

もう少し調べてみる。

ハード側が怪しいということも疑って、Aruduinoでも使用可能なのでそちらでハード側のテストを行ってみました。 Arduinoでやっている例は結構あるので、こちらのほうが楽かなとは思いますが、Unoでは電圧が5V系なのでその部分は注意が必要です。(リーダー系は3.3V系なので)

Arduinoで近距離無線通信 RFID-RC522 NFC by ボクにもわかる地上デジタル

github.com

f:id:ueponx:20170918044058p:plain

実際に動作させてみると…一応、リードできてる?UIDは普通に読めていますがデータブロックは読み取れていません。 (たまにブロックは読み込めることもある…。RFIF側のUIDの読み取りに空振ることもあるぽい…。) ハード的にはかなり怪しいようです。

粘ってみる…その1

更に情報をネットを検索すると

Raspberry PI 3 and RFID-RC522 Problem reading data - Raspberry Pi Forums

なる情報もdtoverlay=spi0-hw-cs/boot/config.txtの設定の追加のようです。

$ sudo vim /boot/config.txt

【変更前】

(略)
# Uncomment some or all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on
(略)

↓ 【変更後】

(略)
# Uncomment some or all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on
dtoverlay=spi0-hw-cs
(略)

dtはDeviceTreeの略のようです、最近ではraspi-configではなく/boot/config.txtで設定を行う様です。 でも、こちらを追加したのですが駄目でした。

粘ってみる…その2

ならばということでそもそもOSを変えてやれってバージョンを2つ落として2015-05-05-raspbian-wheezyにしてみました。 これで動かなかったらハードだろぐらいの気持ちです。

wheezyでは、jessie以降にはなかったDevice Treeの設定はちゃんとraspi-configコマンド内にも有効無効は存在しています。 それを設定すると検索でヒットするものと同じ状態になるみたいです。

$ dmesg  | grep spi
[    6.733677] bcm2708_spi bcm2708_spi.0: master is unqueued, this is deprecated
[    7.008711] bcm2708_spi bcm2708_spi.0: SPI Controller at 0x20204000 (irq 80)

$ lsmod |grep spi
regmap_spi              2307  1 snd_soc_wm8804
spi_bcm2708             6018  0

$ ls /dev/spidev* -l
crw-rw---T 1 root spi 153, 0  11  1970 /dev/spidev0.0
crw-rw---T 1 root spi 153, 1  11  1970 /dev/spidev0.1

ですが、動作しません。ムキー。

f:id:ueponx:20170918155520p:plain

粘ってみる…その3

よくわからない気分になってきたので、更に粘ってみました。今度はIchigoJamです。

fukuno.jig.jp

ドライバーもなにもないので、これでだめならハードが悪い(もうそんな気分ですが)と諦められます。

f:id:ueponx:20170918165753j:plain

結果、駄目でした…読み取り待ちにはなってくれるのですが、それ以上の処理には続かないようです。 このためにIchigoJamの使い方をさらに勉強とは、すごい自分にもやる気があるんだなと思いました(棒)

f:id:ueponx:20170918155910p:plain

終わりに

ハードさえ動けばやりたいこと全部やりきった感あります。 新規にハード買うかして再チャレンジしたいと思います。

f:id:ueponx:20170918160821p:plain

2日使ったのでかなり寝たいです。

RaspberryPiでTwitterのタイムラインを読み上げてみる(Amazon Polly & Twitter & Python)

RaspberryPiでTwitterのタイムラインを読み上げてみる(Amazon Polly & Twitter & Python

以前のエントリーでAmazon Polly(以下Polly)を使っていましたが、流石にもう少しちゃんと動くものを作ってみようかなと思います。具体的にはTwitterのタイムラインを取得してそれを読み上げようと思います。これまでも似たようなものとして、OpenJTalkで読み上げを行っていましたがOpenJTalkでは文章量が長くなると途中できれてしまうということが稀にあり、Pollyではそんなこともないと信じたいのでチャレンジしてみました。(Pollyは有料サービスなんでそんなには使えないですけどね)

f:id:ueponx:20170914195730p:plain f:id:ueponx:20170914195636p:plain

この組み合わせは Polly & Twitterバードの鳥鳥コンビなのはちょっと楽しい。

インストール

TwitterのタイムラインをPythonで取得するtweepyは以前のエントリーでも使っていますが、かなり前の話なので念のためインストールについても書いておきます。といってもpipを使えばそれでOKです。

uepon.hatenadiary.com

$ sudo pip install tweepy
Collecting tweepy
  Downloading tweepy-3.5.0-py2.py3-none-any.whl
Requirement already satisfied: six>=1.7.3 in /usr/lib/python2.7/dist-packages (from tweepy)
Requirement already satisfied: requests>=2.4.3 in /usr/local/lib/python2.7/dist-packages (from tweepy)
Requirement already satisfied: requests-oauthlib>=0.4.1 in /usr/lib/python2.7/dist-packages (from tweepy)
Requirement already satisfied: oauthlib>=0.6.2 in /usr/lib/python2.7/dist-packages (from requests-oauthlib>=0.4.1->tweepy)
Installing collected packages: tweepy
Successfully installed tweepy-3.5.0

とても簡単です。

あとはPollyですが、これでも以前のエントリーを参考にしてインストールしておきます。bot3のインストールを行えばOKです。

uepon.hatenadiary.com

これでPythonからTwitterPollyを使う準備が整いました。

読み上げプログラムを作ってみる

行おうとしているプログラムは基本的には以下の動作から構成されます。

  • Twitterの認証(tweepyモジュールの使用)
  • タイムラインの取得(tweepyモジュールの使用)
  • タイムラインの不要な部分の削除(正規表現:reモジュールの使用)
  • Pollyへテキストの送信とファイルへの保存(bot3へのアクセス)
  • pygameのmixerで再生

こちらがソースの全体となります。(関数化とかしてない…)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tweepy
import re
import datetime

from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError
from contextlib import closing
import os
import sys
import subprocess
import pygame.mixer
import time

# 個別にTwitterクライアント作成時に設定されるCONSUMER_KEY、CONSUMER_SECRETを設定してください
CONSUMER_KEY = 'XXXXXXXXXXXXXXXXXXX'
CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth_url = auth.get_authorization_url()

print('Please authorize: ' + auth_url)
verifier = raw_input('PIN: ').strip()

auth.get_access_token(verifier)

print("ACCESS_KEY = '%s'" % auth.access_token)
print("ACCESS_SECRET = '%s'" % auth.access_token_secret)

ACCESS_KEY = auth.access_token
ACCESS_SECRET = auth.access_token_secret

auth.set_access_token(ACCESS_KEY, ACCESS_SECRET)
api = tweepy.API(auth)
print(api.me().name)

now = datetime.datetime.now()
print(now)

# api.update_status('Tweepy! Sample start! ' + str(now))

session = Session(region_name="us-west-2")
polly = session.client("polly")

public_tweets = api.home_timeline()
for i, tweet in enumerate(public_tweets):
    print('---{}---'.format(i))
    print(tweet.author.screen_name + ":")
    # print(tweet.text)

    text = re.sub('RT', "", tweet.text)
    text = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text = re.sub(r'[##]([\w一-龠ぁ-んァ-ヴーa-z]+)', "", text)
    text = re.sub(r'@([\w]+):', "", text)
    text = re.sub('\s', "", text)

    print(text)

    try:
        response = polly.synthesize_speech(
            Text=text, OutputFormat="mp3", VoiceId="Mizuki")
    except (BotoCoreError, ClientError) as error:
        print(error)
        sys.exit(-1)
    if "AudioStream" in response:
        with closing(response["AudioStream"]) as stream:
            output = "speech.mp3"
            try:
                with open(output, "wb") as file:
                    file.write(stream.read())
            except IOError as error:
                print(error)
                sys.exit(-1)
        print("synthesize_speech OK ->>" + output)
    else:
        print("Could not stream audio")
        sys.exit(-1)

    pygame.init()
    pygame.mixer.init()
    pygame.mixer.music.load("speech.mp3")
    pygame.mixer.music.play()
    print("PLAY")
    while pygame.mixer.music.get_busy() == True:
        continue
    print("END")

    pygame.mixer.music.stop()
    pygame.mixer.quit()
    pygame.quit()

ソースの説明

以下では部分的に説明をしていきます。

Twitterの認証(tweepyモジュールの使用)

以下の部分でTwitterに関する認証を行っています。 CONSUMER_KEYCONSUMER_SECRETTwitterのアプリの作成画面に出ます。これを使って認証用のURLを呼び出し、PIN生成を行っていきます。 表示されるURLをブラウザに与えることでPIN表示まで持っていきます。

f:id:ueponx:20170916083004p:plain

起動すると以下のようなPINの入力待ち画面になります。

f:id:ueponx:20170914222010p:plain

この処理を行っているのが以下の処理になります。

CONSUMER_KEYCONSUMER_SECRETの値がXXXXX…となっていますがTwitterのアプリケーション設定画面の

  • Consumer Key (API Key)
  • Consumer Secret (API Secret)

の値を入れるようにしてください。

f:id:ueponx:20170914222407p:plain

Pythonの処理部分】

# 個別にTwitterクライアント作成時に設定されるCONSUMER_KEY、CONSUMER_SECRETを設定してください
CONSUMER_KEY = 'XXXXXXXXXXXXXXXXXXX'
CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth_url = auth.get_authorization_url()

print('Please authorize: ' + auth_url)
verifier = raw_input('PIN: ').strip()

表示されたURLをクリックすると

f:id:ueponx:20170914222645p:plain

認証画面に遷移します。【連携アプリを認証】をクリックします。(ログインを事前にしていないと、ログインを促された後にこの画面になるかもしれません。)

f:id:ueponx:20170914222912p:plain

すると7桁のPINコードが表示されるので、この値をコンソールに入力します。

PINを入力すると実際に使用するACCESS_TOKENACCESS_TOKEN_SECRETを取得する処理となります。

Pythonの処理部分】

auth.get_access_token(verifier)

print("ACCESS_KEY = '%s'" % auth.access_token)
print("ACCESS_SECRET = '%s'" % auth.access_token_secret)

ACCESS_KEY = auth.access_token
ACCESS_SECRET = auth.access_token_secret

auth.set_access_token(ACCESS_KEY, ACCESS_SECRET)
api = tweepy.API(auth)

これでアプリケーションの使用に必要なACCESS_KEYACCESS_TOKEN_SECRETの取得ができました。あとは認証処理に与えれば完了となりますauth.set_access_token(ACCESS_KEY, ACCESS_SECRET)の部分になります。これでTwitter APIの処理を行える様になりました。 本来であればこれらキーはファイルに保存して再利用するのですが、今回は端折っています。保存する場合にはアクセス権などには注意してください。

タイムラインの取得(tweepyモジュールの使用)

APIの使用ができるようになればタイムラインの取得は簡単です。今回はHomeタイムラインを取得しています。

Pythonの処理部分】

public_tweets = api.home_timeline()
for i, tweet in enumerate(public_tweets):
    print('---{}---'.format(i))
    print(tweet.author.screen_name + ":")
    # 表示する場合には以下のコメントを外してください
    # print(tweet.text)

取得したタイムラインはデフォルト件数は20件になります。必要であれば引数に追加を与えることになりますが、Pollyが有料サービスなのでこのままにしておきます。

タイムラインの不要な部分の削除(正規表現:reモジュールの使用)

タイムラインの文面から不要なURLなどを正規表現で削除して読み上げに適した文面に変換します。さすがにURLを読み上げられても?となってしまうので。

以下は正規表現でURL、【RT】接頭辞、ハッシュタグ、ユーザ名の削除を行いプレーンな文章に変換をして行きます。

Pythonの処理部分】

    text = re.sub('RT', "", tweet.text)
    text = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text = re.sub(r'[##]([\w一-龠ぁ-んァ-ヴーa-z]+)', "", text)
    text = re.sub(r'@([\w]+):', "", text)
    text = re.sub('\s', "", text)

これで完璧というわけではないのですがおおよそ大丈夫かなと思います。

Pollyへテキストの送信とファイルへの保存(bot3へのアクセス)

得られたタイムラインのデータをbot3にアクセスして、Pollyが読み上げした音声のデータを取得していきます。

このあたりは過去エントリー参照で大丈夫でしょう。

uepon.hatenadiary.com

Pythonの処理部分】

    print(text)

    try:
        response = polly.synthesize_speech(
            Text=text, OutputFormat="mp3", VoiceId="Mizuki")
    except (BotoCoreError, ClientError) as error:
        print(error)
        sys.exit(-1)
    if "AudioStream" in response:
        with closing(response["AudioStream"]) as stream:
            output = "speech.mp3"
            try:
                with open(output, "wb") as file:
                    file.write(stream.read())
            except IOError as error:
                print(error)
                sys.exit(-1)
        print("synthesize_speech OK ->>" + output)

この処理が終わるとPollyで生成された音声ファイルはspeech.mp3とされて保存されます。

pygameのmixerで再生

あとは変換された音声を再生するだけです。

Pythonの処理部分】

    pygame.init()
    pygame.mixer.init()
    pygame.mixer.music.load("speech.mp3")
    pygame.mixer.music.play()
    print("PLAY")
    while pygame.mixer.music.get_busy() == True:
        continue
    print("END")

    pygame.mixer.music.stop()
    pygame.mixer.quit()
    pygame.quit()

こちらも先程のエントリーを参考にしてもらえればと思います。(初期化処理・終了処理は毎回しなくてもいいかなとは思いますが。)

終わりに

ようやくPythonを使ってTwitterのタイムラインを取得して読み上げることができました。 まあ、読み上げられた文面もなんとか分かるかなという感じですがAAや絵文字なんかがあると謎の文面になってしまいますね。さすがに仕方ないですね。 長い文章での読み上げがうまくいくなら、ネットワークアクセスがある分遅くなってしまいますのでOpenJTalkの方がいいかも。

コンソールTwitterクライアント```rainbowstream```をつかってみた

コンソールTwitterクライアントrainbowstreamをつかってみた

なんとなく、linux環境でもコンソールでTwitterのタイムラインを見てみたいなあと思って、クライアントをさがしていました。(家で使うのはRaspbianだけなんで気まぐれな欲求なんですが)

その中でもなかなかいいなと思ったのがrainbowstreamでした。

テーマで色分けができたりというのがかなり見やすくていいかなと思いますし、気合を入れれば画面もそれなりにわかるというのもすごいと思います。そういう用途はGUI環境でやればいいとは思いますが。

ってことでインストールしてみました。

rainbowstreamのインストール

以下のリンクを参照にすれば簡単にインストールできます。環境はpythonなので本体はpipでのインストールになりますが、いろいろLibraryの依存があるのでそれも一緒にインストールする必要があります。

github.com

ドキュメントにはvirtualenvを使った方がいいということでしたが、今回は端折ってそのままインストールしています。

$ sudo apt-get install python-dev libjpeg-dev libfreetype6 libfreetype6-dev zlib1g-dev
$ sudo pip install rainbowstream

【参考】 Pipenv & Virtual Environments — The Hitchhiker's Guide to Python

インストールは問題なく終わると思います。(本体以外のLibraryもインストール済みのものがほとんどでした。)

rainbowstreamを実行してみる

実行は以下のようにするだけです。

$ rainbowstream

初回の実行ではTwitterアプリケーションのPIN認証が求められるので

f:id:ueponx:20170913232333p:plain

画面に表示されたURLをクリックしてブラウザで認証を行います。リンクをクリックすると以下のような画面になるので

f:id:ueponx:20170910153740p:plain

【連携アプリを認証】ボタンをクリックします。すると7桁数字のPINが表示されるのでそれをコピーなどしてコンソールに入力します。(画面では伏せています)

※2回目以降の起動では初回起動時の認証情報が~/.rainbow_oauthに格納されるでの認証は不要です。

f:id:ueponx:20170910154556p:plain

するとログインが行われて

f:id:ueponx:20170910154656p:plain

コマンドモードになりますが、このままほっておくと自分のタイムラインが表示されます。

f:id:ueponx:20170910155018p:plain

コマンドマニュアルはこちらになります。

Rainbow Stream — RainbowStream 1.3.7 documentation

おわりに

複数のTLを監視しない範囲であればコマンドラインでの実行でもかなりいいかなと思います。数世代前のPCでLinuxを動かす場合にはこちらは軽快かもしれませんねです。(Aspire one 753 Celeron U3400 / 2GBメモリー / USBメモリブート/ RaspberryPixel86)

本当は、この結果が標準出力されて、それを使ってpollyとかから読み上げでも行おうと思っていたのですが、標準出力はしてくれないようです。残念。 次回では真面目にPythonでTweepyを使ってタイムラインを拾って読み上げをさせてみたいと思います。

yapfの導入

yapfの導入

個人の開発スタイルのような感じですが、Windowsなどでpythonのソースを書いていて、vimへコピー・ペーストをすることがよくあるのですが、何故かインデントのフォーマットが異なってしまうので、そのあとの手間が多いのでなんとかしないと行けないなあと思っていました。(他の言語ならそれほど大きく問題はありませんが、pythonではそういうわけにも行きません。)

そこで、yapfとういうフォーマッタがあることがわかりました。

github.com

googleさん謹製のようです。

yapfのインストール

readmeを見ればわかりますが以下の様にインストールします。

$ pip install yapf
Collecting yapf
  Downloading yapf-0.17.0-py2.py3-none-any.whl (151kB)
    100% |????????????????????????????????| 153kB 1.1MB/s
Installing collected packages: yapf
Successfully installed yapf-0.17.0

raspberrypiでインストールするとyapf~/.local/bin/yapfにインストールされるようです。

実行するとこんな感じになります。

$ ~/.local/bin/yapf --version
yapf 0.17.0

$ ~/.local/bin/yapf --help
usage: yapf [-h] [-v] [-d | -i] [-r | -l START-END] [-e PATTERN]
            [--style STYLE] [--style-help] [--no-local-style] [-p]
            [files [files ...]]

Formatter for Python code.

positional arguments:
  files

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         show version number and exit
  -d, --diff            print the diff for the fixed source
  -i, --in-place        make changes to files in place
  -r, --recursive       run recursively over directories
  -l START-END, --lines START-END
                        range of lines to reformat, one-based
  -e PATTERN, --exclude PATTERN
                        patterns for files to exclude from formatting
  --style STYLE         specify formatting style: either a style name (for
                        example "pep8" or "google"), or the name of a file
                        with style settings. The default is pep8 unless a
                        .style.yapf or setup.cfg file located in one of the
                        parent directories of the source file (or current
                        directory for stdin)
  --style-help          show style settings and exit; this output can be saved
                        to .style.yapf to make your settings permanent
  --no-local-style      don't search for local style definition
  -p, --parallel        Run yapf in parallel when formatting multiple files.
                        Requires concurrent.futures in Python 2.X

テスト

公式サイトのサンプルを使って実験してみます。

github.com

x = {  'a':37,'b':42,

'c':927}

y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo  (     object  ):
  def f    (self   ):
    return       37*-+2
  def g(self, x,y=42):
      return y
def f  (   a ) :
  return      37+-+a[42-x :  y**3]

f:id:ueponx:20170910140531p:plain

$ ~/.local/bin/yapf sample.py
Traceback (most recent call last):
  File "/home/pi/.local/bin/yapf", line 11, in <module>
    sys.exit(run_main())
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 296, in run_main
    sys.exit(main(sys.argv))
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 188, in main
    parallel=args.parallel)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 236, in FormatFiles
    in_place, print_diff, verify)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/__init__.py", line 259, in _FormatFile
    logger=logging.warning)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/yapf_api.py", line 91, in FormatFile
    verify=verify)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/yapf_api.py", line 129, in FormatCode
    tree = pytree_utils.ParseCodeToTree(unformatted_source)
  File "/home/pi/.local/lib/python2.7/site-packages/yapf/yapflib/pytree_utils.py", line 102, in ParseCodeToTree
    tree = parser_driver.parse_string(code, debug=False)
  File "/usr/lib/python2.7/lib2to3/pgen2/driver.py", line 106, in parse_string
    return self.parse_tokens(tokens, debug)
  File "/usr/lib/python2.7/lib2to3/pgen2/driver.py", line 47, in parse_tokens
    for quintuple in tokens:
  File "/usr/lib/python2.7/lib2to3/pgen2/tokenize.py", line 429, in generate_tokens
    ("<tokenize>", lnum, pos, line))
  File "sample.py", line 11
    def g(self, x,y=42):
    ^
IndentationError: unindent does not match any outer indentation level

動かない…エラーメッセージから察するに、Webページのものをコピペして使うとpythonの文法まで崩れてしまっているようです。

Teratermのウインドウにドラッグ・アンド・ドロップすることで実行できる、scpなどでファイル転送をすると…

$ cat sample.py
x = {  'a':37,'b':42,

'c':927}

y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo  (     object  ):
  def f    (self   ):
    return       37*-+2
  def g(self, x,y=42):
      return y
def f  (   a ) :
  return      37+-+a[42-x :  y**3]

$ ~/.local/bin/yapf sample.py
x = {'a': 37, 'b': 42, 'c': 927}

y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')


class foo(object):
    def f(self):
        return 37 * -+2

    def g(self, x, y=42):
        return y


def f(a):
    return 37 + -+a[42 - x:y**3]

うまく実行できたようです。ファイルを変換したものに上書きするには-iをつければいいようです。他にも処理後との差分(diff)を表示する-d再帰的に実行する-rのスイッチがあります。

$ ~/.local/bin/yapf -i sample.py
pi@raspberrypi:~/yapf_sample $ cat sample.py
x = {'a': 37, 'b': 42, 'c': 927}

y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')


class foo(object):
    def f(self):
        return 37 * -+2

    def g(self, x, y=42):
        return y


def f(a):
    return 37 + -+a[42 - x:y**3]

REPL環境での実行でモジュールとして使用することもできる様です。 githubのページでは以下のようになると書いてあるのですが、

>>> from yapf.yapflib.yapf_api import FormatCode  # reformat a string of code

>>> FormatCode("f ( a = 1, b = 2 )")
'f(a=1, b=2)\n'

自分の環境では

$ python
Python 2.7.13 (default, Jan 19 2017, 14:48:08)
[GCC 6.3.0 20170124] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from yapf.yapflib.yapf_api import FormatCode  # reformat a string of code
>>> FormatCode("f ( a = 1, b = 2 )")
(u'f(a=1, b=2)\n', True)
>>>

となりました。python2だったからかな?念のためpython3でも実行してみました。

$ pip3 install yapf
Collecting yapf
  Using cached yapf-0.17.0-py2.py3-none-any.whl
Installing collected packages: yapf
Successfully installed yapf-0.17.0

$ python3
Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170124] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from yapf.yapflib.yapf_api import FormatCode
>>> FormatCode("f ( a = 1, b = 2 )")
('f(a=1, b=2)\n', True)
>>> FormatCode("def g( ):\n    a=1\n    b = 2\n    return a==b", lines=[(1, 1), (2, 3)])
('def g():\n    a = 1\n    b = 2\n    return a==b\n', True)

ユニコード文字列を指すのuのプリフィックス以外は変わったところが無いみたい、あれ?

終わりに

コピペしてフォーマットが少し崩れるぐらいであれば、なんとかできるかなと思いましたが、派手に崩れる(文法的に崩れるレベル)とちょっとむずかしい様です。

RaspberryPiだけでなくWindowsのanaconda環境にもインストールしましたがこっちで使ったほうがいいのかもしれません。(コピペする前にこっちで整形をするという感じ)

Node.jsでRSSのデータを取得する【Windows10、RaspberryPi、DragonBoard】

Node.jsでRSSのデータを取得する【Windows10、RaspberryPi、DragonBoard】

以前からちょっと欲しいなと思っていたのですが、現在のテレビ番組を一覧的に表示できなかなと。テレビをつけてEPGを見ればいいじゃないとというのはもちろんわかっているんですが、もう自分はAmazon Primeなしには生きられない特撮バカですので入力切り替えとかそんなことはもうやりたくありません。(きっぱり)

どうせ、PCつけてコードとか書いているんだしPC上で見れればそれでいいかなと思っていたんです。

テレビを見なければいいという話もあるのかもしれませんが…そういうわけにもいかないので…。

テレビの放送状況を確認するにはテレビ王国というサイトがあります。

f:id:ueponx:20170903163256p:plain

SONYさんが運営しているサービスでHDDレコーダやiEPGなんかでも使われているので比較的確度の高い情報でしょう。

tv.so-net.ne.jp

このサイトは全国のエリアをカバーしているので、自分のような地方の人間にも優しいです。また、現在の放送番組のRSSも公開されているので最高です!

RSSはこんな感じ。

Gガイド.テレビ王国 - 放送中のRSS配信 - 地上波(東京)

あとはこのRSSを読み込んでしまえば…ということになります。

今回は他のシステムとの連携もしたかったのNode.jsコーディングしました。

Node.jsでRSSを読み込む

ネットで検索したところWebのスクレイピングではcheerio-httpcliを使用すると便利そうでした。

qiita.com

cheerio-httpcli

特徴としては以下の様です。

  1. 取得先WEBページの文字コードを自動で判定してHTMLをUTF-8に変換
  2. UTF-8に変換したHTMLをjQueryのように操作可能
  3. フォームの送信やリンクのクリックをエミュレート
  4. Node.jsお馴染みのコールバック形式と最近の流行であるプロミス形式どちらにも対応
  5. 同期リクエスト対応
  6. $(‘img’)要素画像のダウンロード(LazyLoad対応)
  7. $(‘a,img,script,link’)要素のURLを絶対パスで取得可能
  8. ブラウザ指定による簡単User-Agent切り替え機能
  9. 現在のクッキーの内容を簡単に取得(読み取り専用)
  10. XMLドキュメントを自動判別してパース処理を切り替え

半分ぐらいは今回とはあまり関係はないのですが、今後使えそうな機能が多いのでこちらを採用しました。

cheerio-httpcliのインストール

今回の実行環境(Windows10)は以下の通りです。

PS C:\Users\xxx\Documents\node> node -v
v6.11.2
PS C:\Users\xxx\Documents\node> npm -v
3.10.10

インストール作業はnpmを使えば簡単です。(LinuxでもWindowsでも違いはありません。プロンプトが違うぐらいです。)

PS C:\Users\xxx\Documents\node> npm install cheerio-httpcli

問題なくインストールできました。

コーディング

先程のRSSのURLを.fetch()の引数に与えてあとは、コールバック形式かプロミス形式で処理を行えば問題ありません。プロミスってなんだろうと思ったんですが、「非同期の動作を扱う際にコールバックからメソッドチェーンにすることができコールバック地獄を回避することができる」ってことのようです。非同期処理では結構苦労するので、こういう改良が必要なんですね。

今回は規模的にはどちらを選択しても良かったのですが、せっかくなので

【sample.js】

var client = require('cheerio-httpcli');
// かんとーちほーの現在の放送番組
var RSS = "https://tv.so-net.ne.jp/rss/schedulesByCurrentTime.action?group=10&stationAreaId=23";

client.fetch(RSS, {})
.then(function (result){
    if (result.error) { console.log("error"); return; }
    result.$("item").each(function (idx) {
        programName = result.$(this).find("title").text();
        programFrame = result.$(this).find("description").text();
        console.log(programName);        
        console.log(programFrame);
        console.log("---");
    });
    console.log("\n" + "放送中番組のタイトルを取得しました。");
});

これを実行してみます。

PS C:\Users\xxx\Documents\node> node .\sample.js
Hulu傑作シアター[]
9/3 1:302:30 [日テレ(Ch.4)]
---
名棋士が読み解く 藤井聡太 “強さ”の秘密[][]
9/3 1:452:30 [NHK総合・東京(Ch.1)]
---
ワールドプロレスリング
9/3 2:002:30 [テレビ朝日(Ch.5)]
---
お買い物情報
9/3 2:002:30 [TOKYO MX1(Ch.9)]
---
HERO'S[デ]
9/3 2:05~2:45 [フジテレビ(Ch.8)]
---
ランク王国 秋のサキドリSP!
9/3 2:08~3:08 [TBS(Ch.6)]
---
深夜に発見!新shock感~一度おためしください~
9/3 2:10~2:35 [テレビ東京(Ch.7)]
---
放送休止
9/3 2:12~5:00 [NHKEテレ1・東京(Ch.2)]
---

放送中番組のタイトルを取得しました。

うまく行ったようです。これで取得できるのはタイトル、放送枠時間、放送局だけなので内容がほしい場合には RSS上のlinkタグにあるURLを更にスクレイピングする必要があるので少し面倒ですね。

終わりに

あとはうまい感じで表示できればOKですね。

少し欲を出して、番組情報も出せるようにしました。1回目のRSSスクレイピングで詳細情報のリンク先を取得し、 2回目のスクレイピングで取得するという感じなのですが、JavaScriptは非同期実行なので、データにタイミングがコードの想定とはズレてしまうようでした。 Objectに格納しようとしたら格納時にはデータがなく、どこでデータ来るのかを判別するのがちょっと難しかったでの悩んでいました。

最初に書いた

  1. 同期リクエスト対応

こちらのことを思い出して、ググってみました。 以下のリンクに同期的実行に関して情報が乗っていたのでそれを試して見ました。

【参考】 qiita.com

【sample2.js】

var client = require('cheerio-httpcli');
var RSS = "https://tv.so-net.ne.jp/rss/schedulesByCurrentTime.action?group=10&stationAreaId=23";
var programsList = [];

var result1 = client.fetchSync(RSS);
result1.$("item").each(function (idx) {
    var program = new Object();
    var programName = result1.$(this).find("title").text();
    var programFrame = result1.$(this).find("description").text();
    var programLink = result1.$(this).find("link").text();
    var result2 = client.fetchSync(programLink);
    programInfo = result2.$(".subUtileSetting").eq(0).find("p").text();

    program["Name"] = programName;
    program["Frame"] = programFrame;
    program["Link"] = programLink;
    program["Info"] = programInfo; 
    programsList.push(program);
});

programsList.forEach(function(element) {
    console.log("\n--->\n");
    console.log("番組名  :" + element["Name"]);        
    console.log("番組時間 :" + element["Frame"]);        
    console.log("番組リンク:" + element["Link"]);            
    console.log("番組詳細 :" + element["Info"]);
}); 

これを実行させると…

PS C:\Users\xxx\Documents\> node .\sample2.js

--->

番組名  :サンバリュ 「外国人観光客に思い切って注意してみました ニッポンのマナー」[]
番組時間 :9/3 13:1514:15 [日テレ(Ch.4)]
番組リンク:http://tv.so-net.ne.jp/schedule/101040201709031315.action?from=rss
番組詳細 :満開の桜の枝をポッキリ折る!市場で生魚を手でベタベタ触る!世界遺産で環境破壊!目に余るマナー違反の外国人観光客を、勇気を出して注意!すると逆ギレ&驚きの言い訳が

--->

番組名  :45th フジサンケイクラシック 最終日[][]
番組時間 :9/3 13:3014:55 [フジテレビ(Ch.8)]
番組リンク:http://tv.so-net.ne.jp/schedule/101056201709031330.action?from=rss
番組詳細 :世界基準コース富士桜に挑む!
初優勝思い出の舞台で復活V目指す岩田寛!
22歳の若武者が初の頂点へ稲森佑貴!

--->

番組名  :舞台芸術の魅力 第8回「世界の現代演劇-演劇における「20世紀」の意味-」
番組時間 :9/3 13:4514:30 [放送大学1(Ch.12)]
番組リンク:http://tv.so-net.ne.jp/schedule/101088201709031345.action?from=rss
番組詳細 :京都造形芸術大学教授 森山 直人

--->

番組名  :海外出張オトモシマス!「フランス 激レア&絶品!チョコ・ハンター」[][][]
番組時間 :9/3 13:5014:20 [NHK総合・東京(Ch.1)]
番組リンク:http://tv.so-net.ne.jp/schedule/101024201709031350.action?from=rss
番組詳細 :海外出張のプロにオトモして、世界の流行の最前線に潜入する番組。今回の出張は、フランス!激レア絶品チョコの大捜索!パリからブルターニュ地方へ。日本初上陸のチョコ!

--->

番組名  :科捜研の女16 #11[][][]
番組時間 :9/3 13:5514:50 [テレビ朝日(Ch.5)]
番組リンク:http://tv.so-net.ne.jp/schedule/101064201709031355.action?from=rss
番組詳細 :「グルメ格付け殺人!死を呼ぶ裏メニューで人間発火!?」沢口靖子()▽身元不明の外国人男性が殺害された。マリコは 被害者が事件の夜に食事したフレンチ店に鍵があると考え…

--->

番組名  :日本の話芸 林家正雀 落語「怪談牡丹灯籠から お札はがし」[][]
番組時間 :9/3 14:0014:30 [NHKEテレ1・東京(Ch.2)]
番組リンク:http://tv.so-net.ne.jp/schedule/101032201709031400.action?from=rss
番組詳細 :第698回東京落語会から林家正雀さんの「怪談牡丹灯籠から お札はがし」をお送りします(平成29818日(金) 東京・虎ノ門 ニッショーホールで収録)

--->

番組名  :今夜!最大の悲劇がー日曜劇場「ごめん、愛してる」スペシャルダイジェスト[]
番組時間 :9/3 14:0014:50 [TBS(Ch.6)]
番組リンク:http://tv.so-net.ne.jp/schedule/101048201709031400.action?from=rss
番組詳細 :クライマックスに向け大きく動き出す長瀬智也主演の胸に迫る切ない物語「ごめん、愛してる」。そこで後編のストーリー&今夜の見所をご紹介!

--->

番組名  :日曜ミステリー「嫌われ監察官 音無一六3」[] 小日向文世主演[][]
番組時間 :9/3 14:0016:00 [テレビ東京(Ch.7)]
番組リンク:http://tv.so-net.ne.jp/schedule/101072201709031400.action?from=rss
番組詳細 :連続殺人事件の現場に残されたのは、恋愛を詠った百人一首の札。怨恨か?それとも…?監察官・音無一六が“深読み力”で真相を暴く! ≪解説放送あり≫

--->

番組名  :MXショッピング
番組時間 :9/3 14:0014:30 [TOKYO MX1(Ch.9)]
番組リンク:http://tv.so-net.ne.jp/schedule/123608201709031400.action?from=rss
番組詳細 :H2OスチームFX 【スチームクリーナー】

個人的には同期的にやったほうがスッキリしますねw。コードも短いし…。

同じソースコードでRaspberryPiとDragonBoard410Cでも実行してみます。

$ node sample2.js

--->

番組名  :決定!こども囲碁名人~第38回少年少女囲碁大会~
番組時間 :9/3 14:3016:30 [NHKEテレ1・東京(Ch.2)]
番組リンク:http://tv.so-net.ne.jp/schedule/101032201709031430.action?from=rss
番組詳細 :囲碁を志す全国の小・中学生にとって日本一の栄冠をかけたあこがれの舞台「少年少女囲碁大会・全国大会」今年も、未来の井山七冠を夢見るこどもたちが熱い戦いを繰り広げた

--->

番組名  :路線バスで寄り道の旅[]
番組時間 :9/3 15:4016:30 [テレビ朝日(Ch.5)]
番組リンク:http://tv.so-net.ne.jp/schedule/101064201709031540.action?from=rss
番組詳細 :日頃秒刻みスケジュールに疲れ気味の徳さん御一行。のんび~り旅を味わうために選んだのは“路線バス"。今回、目指すのは桜木町~馬車道~山下公園で横浜の裏 通りを散策旅

--->

番組名  :大捜索ドキュメント! 屋久島“伝説の超巨大杉”[字][再]
番組時間 :9/3 16:00~16:50 [NHK総合・東京(Ch.1)]
番組リンク:http://tv.so-net.ne.jp/schedule/101024201709031600.action?from=rss
番組詳細 :世界遺産・屋久島で語られる『超巨大杉伝説』。縄文杉を超える杉の大捜索に挑んだ。最新航空調査で森をスキャンし、秘境を大冒険。ついに捜索隊は驚きの巨大杉と遭遇する!

--->

番組名  :グラチャンバレー1番は誰?開幕直前SP[字]
番組時間 :9/3 16:00~16:55 [日テレ(Ch.4)]
番組リンク:http://tv.so-net.ne.jp/schedule/101040201709031600.action?from=rss
番組詳細 :9月5日(火)開幕、グラチャンバレー!徳井義実&佐藤栞里がバレー全日本女子を直撃!気になる噂の真相を選手が激白!

--->

番組名  :なるほど!今うなぎが食べたくなるテレビ【旬は秋!画面から香ばしい香りが?!】[解][字]
番組時間 :9/3 16:00~17:00 [TBS(Ch.6)]
番組リンク:http://tv.so-net.ne.jp/schedule/101048201709031600.action?from=rss
番組詳細 :芸能界のグルメ王・渡部建が名店の秘密に迫る!★芸能界屈指の料理人・梅沢富美男が安くて美味しいうなぎレシピに挑戦!★幻の「青うなぎ」を求めて漁に出るのはみやぞん!

--->

番組名  :YOUは何しに日本へ?予習復習▽日本茶&歌声に熱狂YOUマジ惚れ来日大騒ぎ[字]
番組時間 :9/3 16:00~17:05 [テレビ東京(Ch.7)]
番組リンク:http://tv.so-net.ne.jp/schedule/101072201709031600.action?from=rss
番組詳細 :本気で日本茶学びたい!…20歳青年YOUが本場静岡でガチ住み込み修行…超高級茶葉に昇天!?■遠い異国で人生変えた…坂本九匹敵神歌にYOUが全力マジ惚れ旅■ベビ ーメタル

--->

番組名  :いただきハイジャンプ 土曜お昼にお引っ越しSP[字]
番組時間 :9/3 16:00~17:25 [フジテレビ(Ch.8)]
番組リンク:http://tv.so-net.ne.jp/schedule/101056201709031600.action?from=rss
番組詳細 :(予定変更の場合あり)
様々な一大事にJUMPが立ち向かう▽番組引っ越し記念で人気企画を厳選!6歳少年が苦手克服に挑戦…涙のワケとは?

--->

番組名  :お買い物情報
番組時間 :9/3 16:00~16:30 [TOKYO MX1(Ch.9)]
番組リンク:http://tv.so-net.ne.jp/schedule/123608201709031600.action?from=rss
番組詳細 :トゥルースリーパープレミアケア 【低反発マットレス】

--->

番組名  :障害を知り共生社会を生きる 第8回「難病を知る」
番組時間 :9/3 16:00~16:45 [放送大学1(Ch.12)]
番組リンク:http://tv.so-net.ne.jp/schedule/101088201709031600.action?from=rss
番組詳細 :愛知県立大学教授 吉川 雅博 フリーアナウンサー 岡 知沙登

問題なく動作します!まあNode.jsなんで違いは無いはずですけどね。

うまくいったようです! これで快適なAmazonPrimeの特撮ライフがおくれそうです。

Amazon Rekognitionを使ってみる

Amazon Rekognitionを使ってみる

f:id:ueponx:20170903014828p:plain

Amazon Pollyに引き続いて、Amazon RekognitionCLIで使用してみようと思います。詳細は以下のリンクにあります。

製品の詳細 - Amazon Rekognition | AWS

Rekognition では、画像内の物体、シーン、および顔を検出できます。顔を検索および比較し、有名人を認識し、不適切なコンテンツを識別することもできます。

画像認識系のものですね。MicrosoftさんのComputer Vision APIGoogleさんのCloud Vision APIに該当するもののようです。(あってる?)

Computer Vision APIazure.microsoft.com

【Cloud Vision APIcloud.google.com

最近たくさんのAPIが出てきているのであっているのか不安になってきます。 ネットを見ると機能対決に関しても面白いですが、時間とともに学習されていくこともあるので現在の状況はどうなんでしょうか。

qiita.com

AWS CLIの設定に関して

AWS CLIに関しては以前のエントリでインストールしてあると思いますので説明は省略します。

uepon.hatenadiary.com

Amazon Rekognitionを使用するためのIAMの設定する

Polly使用時と同様にAWSマネージメントコンソールから認証情報を設定する必要があります。具体的にはマネージメントコンソールからIAMを設定する必要があります。

IAMの画面からユーザの追加を選択します。今回のユーザー名はRekognition_userとして【プログラムによるアクセス】にチェックを入れ、画面下の【次のステップ:アクセス権限】ボタンをクリックします。

f:id:ueponx:20170903003330j:plain

作成されたRekognition_userの権限を追加する画面です。

f:id:ueponx:20170903003657j:plain

アクセス権限に【既存のポリシーを直接アタッチ】を選択し、フィルターの検索ボックスにRekognitionと入力して検索をします。

f:id:ueponx:20170903003748j:plain

f:id:ueponx:20170903003807j:plain

すると

  • AmazonRekognitionFullAccess
  • AmazonRekognitionReadOnlyAccess

以上の2つが候補に上がります。今回使用するポリシーはAmazonRekognitionFullAccessというポリシーになりますので、にチェックをいれて、【次のステップ:確認】ボタンをクリックします。(注意)実際には、適切な権限を与えるようにしてください。

ボタンをクリックすると、作成内容の確認画面になります。問題なければ【ユーザの作成】ボタンをクリックします。

f:id:ueponx:20170903004242j:plain

成功すると以下のような画面に遷移します。

f:id:ueponx:20170903004344j:plain

ユーザ名、アクセスキーID、シークレットアクセスキーが表示されますが、画面の中ほどにある【.csvのダウンロード】ボタンをクリックすれば、表示された情報が含まれたCSVファイルがダウンロードできるのでそれをダウンロードしておきます。この情報をあとの手順で使用します。

Amazon RekognistionAWS CLIから使用する

AMIの設定が完了したのでCLIから使用してみたいと思います。初回なのでaws configureコマンドで設定を行います。

PS C:\Users\xxx> aws configure
AWS Access Key ID [None]: <ダウンロードしたCSVに記載>
AWS Secret Access Key [None]: <ダウンロードしたCSVに記載>
Default region name [None]: us-west-2
Default output format [None]: json

Pollyの設定が残っている可能性もあるので、自分は一度設定をクリアしています。書き換える場合にはNoneとなっている部分に旧設定が入力されています。

注意点としてはRekognitionが使えるリージョンは以下の通りです。これ以外のリージョンでは使えないので注意してください。ただし、CLIのオプションで設定すればDefault設定が違っていても問題はないと思います。リージョンが異なる状態で使用すると対応していませんという旨のエラーが発生します。

リージョン名 値?
US East (N. Virginia) us-east-1
US West (Oregon) us-west-2
EU (Ireland) eu-west-1

Configureの設定が終わったので早速使用してみたいと思います。

Amazon Rekognitionのドキュメントを読むと

【参照】 docs.aws.amazon.com

AWS CLI を使用して Amazon Rekognition オペレーションを呼び出す場合、呼び出しの一部として画像のバイトを渡すことはサポートされていません。最初に Amazon S3 バケットに画像をアップロードし、次にアップロードした画像を参照するオペレーションを呼び出します。

と書いてあります。CLI経由での仕様ではバイト列の直接送信はできず、S3で画像ファイルにアクセスするしかないようです。 画像はlenaさんを使用しました。

f:id:ueponx:20170903010215j:plain

S3のBucketを作成し(Bucket名:20170831-image-sample)、画像ファイル(画像ファイル名:lena.jpg)を保存しておきたいと思います。 以下のようにBucketを作成して

f:id:ueponx:20170903010912j:plain

CLI経由で画像ファイルを転送してみます。

PS C:\Users\xxx> aws s3 cp C:\Users\xxx\lena.jpg s3://20170831-image-sample/
upload failed: Desktop\lena.jpg to s3://20170831-image-sample/lena.jpg An error occurred (AccessDenied) when calling the PutObject operation: Access Denied

あれ?そうか、IAMの設定にS3の操作権限がありませんでした。

先程設定したIAMにS3FullAccessのポリシーを追加します。アクセス権限に【既存のポリシーを直接アタッチ】を選択し、S3のフィルタをかけるとAmazonS3FullAccessが表示されると思うのでチェックを入れて追加をします。

f:id:ueponx:20170903011116j:plain

【アクセス権限の追加】ボタンをクリックします。

f:id:ueponx:20170903011411j:plain

以下のように権限が追加されてば問題はありません。

f:id:ueponx:20170903011535j:plain

では改めて、コピーをします。

PS C:\Users\xxx> aws s3 cp lena.jpg s3://20170831-image-sample/
upload: Desktop\lena.jpg to s3://20170831-image-sample/lena.jpg

無事にコピーできたようです。マネージメントコンソール上からでも以下のようにアクセスできました。

f:id:ueponx:20170903011723j:plain

(※)基本的にSDK経由でアクセスする場合にはS3上にあるファイルである必要は必ずしもありません。ただし、AWS CLI経由の場合にはS3である必要があるとのことでした。そのため権限にもS3FullAccessが必要になります。

さて、S3に画像ファイルの準備ができたのでRekognitionを使用してみます。 ここで使用しているのはdetect-labelsになります。入力として渡した画像内の物体、概念、シーンを検出してテキスト情報で返します。

PS C:\Users\xxx> aws rekognition detect-labels --image "S3Object={Bucket=20170831-image-sample,Name=lena.jpg}"
{
    "Labels": [
        {
            "Confidence": 99.27481079101562,
            "Name": "Human"
        },
        {
            "Confidence": 99.27967071533203,
            "Name": "People"
        },
        {
            "Confidence": 99.2796859741211,
            "Name": "Person"
        },
        {
            "Confidence": 53.77459716796875,
            "Name": "Female"
        },
        {
            "Confidence": 53.77459716796875,
            "Name": "Girl"
        },
        {
            "Confidence": 53.77459716796875,
            "Name": "Woman"
        },
        {
            "Confidence": 52.33949279785156,
            "Name": "Hat"
        },
        {
            "Confidence": 51.73891830444336,
            "Name": "Cap"
        },
        {
            "Confidence": 51.73891830444336,
            "Name": "Sun Hat"
        },
        {
            "Confidence": 51.37664031982422,
            "Name": "Face"
        },
        {
            "Confidence": 51.37664031982422,
            "Name": "Selfie"
        }
    ],
    "OrientationCorrection": "ROTATE_0"
}

Human、Person、Girl、Woman、Hat、Face、Selfieなど、この画像にあった検出ができているようです。Json形式で返されているのであとはこれを編集するだけですね。

detect-labels以外にもdetect-moderation-labelsという機能もあります。 Moderation(画像の「節度」)を判定する機能です。 具体的には、不適切な画像(例えばヌードや、露骨ないやらしさなど)を判定することができる機能です。

lenaさんのオリジナル画像はアレな画像なので、先程の顔だけの画像(lena.jp)と全身像の画像(lena2.jpg)でdetect-moderation-labelsを判定してみます。

【全身の画像(一応モザイクはかけました。)】 f:id:ueponx:20170903013037j:plain

【顔だけの画像】

PS C:\Users\xxx> aws rekognition detect-moderation-labels --image "S3Object={Bucket=20170831-image-sample,Name=lena.jpg}"
{
    "ModerationLabels": []
}

【全身の画像】

PS C:\Users\xxx> aws rekognition detect-moderation-labels --image "S3Object={Bucket=20170831-image-sample,Name=lena2.jpg}"
{
    "ModerationLabels": [
        {
            "Confidence": 59.9111328125,
            "ParentName": "",
            "Name": "Suggestive"
        },
        {
            "Confidence": 59.9111328125,
            "ParentName": "Suggestive",
            "Name": "Revealing Clothes"
        }
    ]
}

全身の画像ではRevealing Clothesと判別されています。正解です。

おわりに

Amazon RekognitionをAWS CLIから操作してみました。CLIではS3に画像データを置く必要があるので今ひとつですが、SDK経由であればそのようなことはないようです。画像の送信にBASE64エンコードに対応した言語であればすんなり行けるそうです。(ほとんどがOK)

個人的にはその他のサービスに比べてちょっと物足りないかな…こういうものなんでしょうか。

Amazon PollyをPythonから使ってみる

Amazon PollyをPythonから使ってみる

f:id:ueponx:20170831234755p:plain

前回のエントリーではAWS CLIからAmazon Pollyをコールしてみましたが、今度はpythonから呼び出してみたいと思います。

uepon.hatenadiary.com

Pollyに関しては各言語のドキュメントもサンプルもそろっているので行けると思います。.netからも触れるので自分の場合にはそっちからやればよかったなとは少し思いました。

【PollyのSDKのページ】

開発者用リソース - Amazon Polly | AWS

Amazon Polly(Python編)

こう書くとこのエントリー中にほかの言語も入りそうですが、Pythonのみですw。

インストール

github.com

Boto is the Amazon Web Services (AWS) SDK for Python, which allows Python developers to write software that makes use of Amazon services like S3 and EC2. Boto provides an easy to use, object-oriented API as well as low-level direct service access.

BotoAWSのPythonSDKで、Lowレベルと同等のアクセスができるようになってるってことのようです。

一応、インストールの項目通りにやってみると…

PS C:\> python --version
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
PS C:\> pip -V
pip 9.0.1 from C:\Users\xxx\Anaconda3\lib\site-packages (python 3.6)
PS C:\> pip install boto
Requirement already satisfied: boto in c:\users\xxx\anaconda3\lib\site-packages

メッセージをみると既にAnaconda環境には入っているようなのですが、サンプルソース上でimportされているのってboto3じゃなかったっけ?と思い、 スタートメニューから【Anaconda Prompt】を起動して、以下のようにインストールしてみました。

(C:\Users\xxx\Anaconda3) C:\Users\xxx>pip install boto3
Collecting boto3
  Downloading boto3-1.4.7-py2.py3-none-any.whl (128kB)
    100% |████████████████████████████████| 133kB 2.2MB/s
Collecting s3transfer<0.2.0,>=0.1.10 (from boto3)
  Downloading s3transfer-0.1.10-py2.py3-none-any.whl (54kB)
    100% |████████████████████████████████| 61kB ...
Collecting jmespath<1.0.0,>=0.7.1 (from boto3)
  Downloading jmespath-0.9.3-py2.py3-none-any.whl
Collecting botocore<1.8.0,>=1.7.0 (from boto3)
  Downloading botocore-1.7.0-py2.py3-none-any.whl (3.6MB)
    100% |████████████████████████████████| 3.6MB 225kB/s
Requirement already satisfied: docutils>=0.10 in c:\users\xxx\anaconda3\lib\site-packages (from botocore<1.8.0,>=1.7.0->boto3)
Requirement already satisfied: python-dateutil<3.0.0,>=2.1 in c:\users\xxx\anaconda3\lib\site-packages (from botocore<1.8.0,>=1.7.0->boto3)
Requirement already satisfied: six>=1.5 in c:\users\xxx\anaconda3\lib\site-packages (from python-dateutil<3.0.0,>=2.1->botocore<1.8.0,>=1.7.0->boto3)
Installing collected packages: jmespath, botocore, s3transfer, boto3
Successfully installed boto3-1.4.7 botocore-1.7.0 jmespath-0.9.3 s3transfer-0.1.10

予想通りboto3はインストールされてなかった…サンプルソースでは普通に使用しているのに。

先程のページにはこんな記述が、今後はこっちをみてねってことっぽい。

Boto3, the next version of Boto, is now stable and recommended for general use. It can be used side-by-side with Boto in the same project, so it is easy to start using Boto3 in your existing projects as well as new projects. Going forward, API updates and all new feature work will be focused on Boto3.

github.com

C:\Users\xxx> python
Python 3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from boto3 import Session
>>>

一応、Anaconda環境でないPowerShellのREPLでも動作したので大丈夫です。

使ってみる

ドキュメントはこちら↓

Boto 3 Documentation — Boto 3 Docs 1.4.7 documentation Amazon Polly のドキュメント

BotoAWSサービス制御用のSDKなのでドキュメントもS3EC2などの項目も入っています(というかそっちがメイン)。

上記のリンクからPollyの項目をみつけます。

Polly — Boto 3 Docs 1.4.7 documentation

公式ドキュメントをみると以下のような記載する旨が書かれています。

# Create a client using the credentials and region defined in the [adminuser]
# section of the AWS credentials file (~/.aws/credentials).
session = Session(profile_name="adminuser")
polly = session.client("polly")

ここでいう~/.aws/credentialsaws configure実行時にいれた情報が入ります。credentialsは認証情報、同じディレクトリにある~/.aws/configはregion情報が含まれています。 初期値はdefaultユーザになっているのでドキュメントに従うのであれば

【変更前】

session = Session(profile_name="adminuser")

【変更後】

session = Session(profile_name="default")

になるのですが、defalutの場合には省略できるようで、

polly = boto3.client("polly")

これでもいいのかなと思います。 【参考】 Session — Boto 3 Docs 1.4.7 documentation

もし、リージョン設定が複数跨る場合には(例えば、同時使用するS3とリージョンが異なる場合など)

from boto3 import Session
...
session = Session(region_name="us-west-2")
polly = session.client("polly")

となります。引数で渡しても問題ないようです。大体の基本形はこんな感じになると思います。

from boto3 import Session
...
# 個人的にはリージョンは指定する方が無難かなと思います。
session = Session(region_name="us-west-2")
polly = session.client("polly")
try:
  # TTSの機能をここで呼び出す。CLIの引数はここで指定するイメージ
  response = polly.synthesize_speech(Text="Hello world!", OutputFormat="mp3", VoiceId="Joanna")
except (BotoCoreError, ClientError) as error:
 ...

サンプルコードを眺めながら触ってこんな感じに編集しました。 mainの部分はなくてもいいかなと思います。

【ドキュメントサンプル写経版・改】

"""Getting Started Example for Python 2.7+/3.3+"""
from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError
from contextlib import closing
import os
import sys
import subprocess
from tempfile import gettempdir
# Create a client using the credentials and region defined in the [adminuser]
# section of the AWS credentials file (~/.aws/credentials).
session = Session(region_name="us-west-2")
polly = session.client("polly")
try:
    # Request speech synthesis
    response = polly.synthesize_speech(Text="Hello world!", OutputFormat="mp3", VoiceId="Joanna")
except (BotoCoreError, ClientError) as error:
    # The service returned an error, exit gracefully
    print(error)
    sys.exit(-1)
# Access the audio stream from the response
if "AudioStream" in response:
    # Note: Closing the stream is important as the service throttles on the
    # number of parallel connections. Here we are using contextlib.closing to
    # ensure the close method of the stream object will be called automatically
    # at the end of the with statement's scope.
    with closing(response["AudioStream"]) as stream:
        # output = os.path.join(gettempdir(), "speech.mp3") # サンプルではtmpフォルダになるけど、わかりにくい場所になるので下記に変更
        output = "speech.mp3"
        try:
            # Open a file for writing the output as a binary stream
            with open(output, "wb") as file:
                file.write(stream.read())
        except IOError as error:
            # Could not write to file, exit gracefully
            print(error)
            sys.exit(-1)
        print("create OK>>" + output)
else:
    # The response didn't contain audio data, exit gracefully
    print("Could not stream audio")
    sys.exit(-1)

ちょっと戸惑ったのはpsynthesize_speech()の戻り値がストリーム(AudioStream)になっているので、それをファイルに書き込む必要があるという点かなと思います。with closing()...の下りはC#とかでいうところのIDisposableインターフェイスを持ったオブジェクトのusing (FileStream fs = new FileStream(…)と同じような表現のようです。

あとサンプルではoutput = os.path.join(gettempdir(), "speech.mp3")のようにテンポラリ領域にファイルを入れるようにしているのですが。Windows10の場合にはかなりわかりにくいところに保存されます。C:\Users\【ユーザ名】\AppData\Local\Tempに格納されます。さすがにわかりにくいので今回はテンポラリは使用せずにカレントに保存するように変えています。

コメントがあるので冗長に感じますが、コメントを除いて簡略化するとこんな感じになります。

【シンプル版サンプル・日本語版】

from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError
from contextlib import closing
import os
import sys
import subprocess

session = Session(region_name="us-west-2")
polly = session.client("polly")
try:
    response = polly.synthesize_speech(Text="ひとっ走り付き合えよ", OutputFormat="mp3", VoiceId="Mizuki")
except (BotoCoreError, ClientError) as error:
    print(error)
    sys.exit(-1)
if "AudioStream" in response:
    with closing(response["AudioStream"]) as stream:
        output = "speech.mp3"
        try:
            with open(output, "wb") as file:
                file.write(stream.read())
        except IOError as error:
            print(error)
            sys.exit(-1)
        print("synthesize_speech OK ->>" + output)
else:
    print("Could not stream audio")
    sys.exit(-1)

そこそこわかりやすくなったような気がします。あとはmpg321などを外部コマンドで呼び出すか、pygameにmp3を渡せばそのまま音声出力ができるかなと思います。(後述)

【参考】 uepon.hatenadiary.com

おまけ【RaspberryPiでもPython経由でAmazon Polly】

RaspberryPiからも使ってみます。

インストール

$ pip install boto3
Collecting boto3
  Downloading boto3-1.4.7-py2.py3-none-any.whl (128kB)
    100% |????????????????????????????????| 133kB 1.0MB/s
Collecting botocore<1.8.0,>=1.7.0 (from boto3)
  Downloading botocore-1.7.1-py2.py3-none-any.whl (3.6MB)
    100% |????????????????????????????????| 3.6MB 40kB/s
Collecting jmespath<1.0.0,>=0.7.1 (from boto3)
  Using cached jmespath-0.9.3-py2.py3-none-any.whl
Collecting s3transfer<0.2.0,>=0.1.10 (from boto3)
  Using cached s3transfer-0.1.10-py2.py3-none-any.whl
Collecting python-dateutil<3.0.0,>=2.1 (from botocore<1.8.0,>=1.7.0->boto3)
  Using cached python_dateutil-2.6.1-py2.py3-none-any.whl
Collecting docutils>=0.10 (from botocore<1.8.0,>=1.7.0->boto3)
  Using cached docutils-0.14-py2-none-any.whl
Collecting futures<4.0.0,>=2.2.0; python_version == "2.6" or python_version == "2.7" (from s3transfer<0.2.0,>=0.1.10->boto3)
  Using cached futures-3.1.1-py2-none-any.whl
Collecting six>=1.5 (from python-dateutil<3.0.0,>=2.1->botocore<1.8.0,>=1.7.0->boto3)
  Using cached six-1.10.0-py2.py3-none-any.whl
Installing collected packages: six, python-dateutil, docutils, jmespath, botocore, futures, s3transfer, boto3
Successfully installed boto3-1.4.7 botocore-1.7.1 docutils-0.14 futures-3.1.1 jmespath-0.9.3 python-dateutil-2.6.1 s3transfer-0.1.10 six-1.10.0

実行

$ python polly_simple.py
synthesize_speech OK ->>speech.mp3
$ ls speech.mp3
speech.mp3

ただし、このやり方はpython2.7で動作させているのでShebangとマジックコメントをつけないと日本語で死ぬとエラーがでると思います。下記のようなエラーが出た場合対応する必要があります。

$ python polly_simple.py
  File "polly_simple.py", line 11
SyntaxError: Non-ASCII character '\xe3' in file polly_simple.py on line 11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

【python2なコード】

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError
from contextlib import closing
import os
import sys

session = Session(region_name="us-west-2")
polly = session.client("polly")
try:
    response = polly.synthesize_speech(Text="ひとっ走り付き合えよ", OutputFormat="mp3", VoiceId="Mizuki")
except (BotoCoreError, ClientError) as error:
    print(error)
    sys.exit(-1)
if "AudioStream" in response:
    with closing(response["AudioStream"]) as stream:
        output = "speech.mp3"
        try:
            with open(output, "wb") as file:
                file.write(stream.read())
        except IOError as error:
            print(error)
            sys.exit(-1)
        print("synthesize_speech OK ->>" + output)
else:
    print("Could not stream audio")
    sys.exit(-1)

python3系での実行

もしPython3系で動作させるならインストールはpipではなくpip3で行います。実行もpythonではなくpython3で実行します。

【インストール】

$ pip3 install boto3
Collecting boto3
  Using cached boto3-1.4.7-py2.py3-none-any.whl
Collecting s3transfer<0.2.0,>=0.1.10 (from boto3)
  Using cached s3transfer-0.1.10-py2.py3-none-any.whl
Collecting jmespath<1.0.0,>=0.7.1 (from boto3)
  Using cached jmespath-0.9.3-py2.py3-none-any.whl
Collecting botocore<1.8.0,>=1.7.0 (from boto3)
  Using cached botocore-1.7.1-py2.py3-none-any.whl
Collecting docutils>=0.10 (from botocore<1.8.0,>=1.7.0->boto3)
  Downloading docutils-0.14-py3-none-any.whl (543kB)
    100% |????????????????????????????????| 552kB 489kB/s
Collecting python-dateutil<3.0.0,>=2.1 (from botocore<1.8.0,>=1.7.0->boto3)
  Using cached python_dateutil-2.6.1-py2.py3-none-any.whl
Collecting six>=1.5 (from python-dateutil<3.0.0,>=2.1->botocore<1.8.0,>=1.7.0->boto3)
  Using cached six-1.10.0-py2.py3-none-any.whl
Installing collected packages: docutils, jmespath, six, python-dateutil, botocore, s3transfer, boto3
Successfully installed boto3-1.4.7 botocore-1.7.1 docutils-0.14 jmespath-0.9.3 python-dateutil-2.6.1 s3transfer-0.1.10 six-1.10.0

$ python3 polly_simple3.py
synthesize_speech OK ->>speech.mp3

【Python3用のソース】…このソースではpygameを使って再生まで行っています。pygameではイベントハンドラpygame.event.get():)を使って再生中の状態をイベントで取得できますが、画面のない場合にはエラーとなってしまします(コンソールでは実行できない)。そのため、pygame.mixer.music.get_busy()を使って再生中であるか否かを判別しています。 イベントが使用できない点は以下に記載があります。

Pygame handles all it’s event messaging through an event queue. The routines in this module help you manage that event queue. The input queue is heavily dependent on the pygame display module. If the display has not been initialized and a video mode not set, the event queue will not really work. pygame.event — Pygame v1.9.2 documentation

【Python3用のソース】

from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError
from contextlib import closing
import os
import sys
import pygame.mixer

session = Session(region_name="us-west-2")
polly = session.client("polly")
try:
    response = polly.synthesize_speech(Text="ひとっ走り付き合えよ", OutputFormat="mp3", VoiceId="Mizuki")
except (BotoCoreError, ClientError) as error:
    print(error)
    sys.exit(-1)
if "AudioStream" in response:
    with closing(response["AudioStream"]) as stream:
        output = "speech.mp3"
        try:
            with open(output, "wb") as file:
                file.write(stream.read())
        except IOError as error:
            print(error)
            sys.exit(-1)
        print("synthesize_speech OK ->>" + output)
else:
    print("Could not stream audio")
    sys.exit(-1)
pygame.init()
pygame.mixer.init()
pygame.mixer.music.load("speech.mp3")
pygame.mixer.music.play()
while pygame.mixer.music.get_busy() == True:
    continue
pygame.mixer.music.stop()
pygame.mixer.quit()
pygame.quit()

【実行】

from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError
from contextlib import closing
import os
import sys
import pygame.mixer

session = Session(region_name="us-west-2")
polly = session.client("polly")
try:
    response = polly.synthesize_speech(Text="ひとっ走り付き合えよ", OutputFormat="mp3", VoiceId="Mizuki")
except (BotoCoreError, ClientError) as error:
    print(error)
    sys.exit(-1)
if "AudioStream" in response:
    with closing(response["AudioStream"]) as stream:
        output = "speech.mp3"
        try:
            with open(output, "wb") as file:
                file.write(stream.read())
        except IOError as error:
            print(error)
            sys.exit(-1)
        print("synthesize_speech OK ->>" + output)
else:
    print("Could not stream audio")
    sys.exit(-1)
pygame.init()
pygame.mixer.init()
pygame.mixer.music.load("speech.mp3")
pygame.mixer.music.play()
while pygame.mixer.music.get_busy() == True:
    continue
pygame.mixer.music.stop()
pygame.mixer.quit()
pygame.quit()

うまく音声ファイルの再生までできました。

終わりに

今回はAmazon PollyをPythonから触るPython編というエントリーとしていますが、その多言語からでもドキュメントやサンプルがしっかりしているので問題なく使えると思います。CLIだけでなくいろいろな言語から呼び出せることで、ツイッターからの読み上げなんていうのも簡単にできそうですね。