fswebcamで撮影した画像をtwythonを使ってツイートする (アプリ開発)

# 動機
元々は、文ちゃん（文鳥）の住んでるケージ周辺の日中の環境（温度・湿度）をモニターしたかったのが始まり。
その為、ラズパイでLチカから勉強し始めたんだけど、色々自理由をつけていつの間にかフェードアウト。。。
しばらくして、ラズパイとUSBカメラが接続できて、写真をキャプチャ出きる事を知り、
何かできそうなのでやってみようと方向転換。

よって今回の最終目標を、
写真で文ちゃんの日中の挙動をカメラで定期的に撮影し、Twitterに投稿する。そして仕事場から見て楽しむ:heart:
とした。

# 目次
1. 使った機材
2. 環境構築（ラズパイ＆開発環境）　
3. アプリ開発
4. コードの置き場所とbotの紹介

[前回](https://qiita.com/hondasports8/items/dec3221ad5f4bb7d691b)は、目次2. 環境構築（ラズパイ＆開発環境）まで説明したので、当記事は

* 3. アプリ開発
* 4. コードの置き場所とbotの紹介についてまとめる。

# この記事で伝えたいこと
1. 仕組み
2. ハマったポイント

# この記事で伝えないこと
1. [Twitter Application Management](https://apps.twitter.com/)でアプリの登録をする手順

# 仕組み
1. crontabはshellをキック
2. shellは、記述しているPythonスクリプトを実行
3. Pythonスクリプトは、設定ファイルからtwitterで作成した
    * Consumer Key
    * Consumer Secret
    * Access Token
    * Access Token Secretを読み取る。
4. fswebcamを実行し、撮影を行いファイルを保存する。
5. 保存したファイルを読み込む。
6. twythonを使ってtweetする。

## 1. crontabはshellをキック

何の変哲も無い設定。

``` crontab
raspberrypi% crontab -l
# hour:7-18 week:mon-fri
0,30 7-18 * * 1,2,3,4,5 /home/pi/code/git/twitterSample/twitterSample.sh >/dev/null 2>&1
```

## 2. shellは、中に記述しているPythonスクリプトを実行
開発はvirtualenvで構築した環境で行っていたので、pythonを直接実行するのではなくshellでvirutalenvのコマンドを実行しPythonを実行するようにした。

```
#!/bin/zsh
export CURRENT_PATH=`echo $(cd $(dirname $0) && pwd)`
export ENV_NAME=twitterSample
export VIRTUALENV_PATH=$CURRENT_PATH/$ENV_NAME

source $VIRTUALENV_PATH/bin/activate
python $CURRENT_PATH/twitterSample.py
```
参考リンク：[[Shell] $(cd $(dirname $0) && pwd) を理解する](http://blog.kjirou.net/p/506)

# 3以降はコード内で説明

Consumer Keyなどはtwitter.configとして記述

```
(twitterSample) raspberrypi% cat twitter.config
[KEY]
CONSUMER_KEY = xxxxxxxxxxxxxxxxxxxx
CONSUMER_SECRET = xxxxxxxxxxxxxxxxxxxx
ACCESS_TOKEN = xxxxxxxxxxxxxxxxxxxx
ACCESS_TOKEN_SECRET = xxxxxxxxxxxxxxxxxxxx
```

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

import subprocess
import sys
import configparser
from PIL import Image
import io
import os
from twython import Twython, TwythonError
from datetime import datetime

def tweet():

        // 3. Pythonスクリプトは、設定ファイルからtwitterで作成した云々の箇所
        // import configparserが必要。
        // twitter.configを読み込みconfig内にDictionaryとして展開。

        config = configparser.ConfigParser()
        config.read(os.path.dirname(os.path.abspath(__file__)) + '/twitter.config')

        twitter = Twython(
                config['KEY']['CONSUMER_KEY'],
                config['KEY']['CONSUMER_SECRET'],
                config['KEY']['ACCESS_TOKEN'],
                config['KEY']['ACCESS_TOKEN_SECRET']);

        #try:
        #       twitter.update_status(status='Tweet Sample')
        #except TwythonError as e:
        #       print(e)

        image_io = None
        try:

                // 4. fswebcamを実行し、撮影を行いファイルを保存するの箇所。
                // import subprocessが必要。

                subprocess.run('fswebcam -F 1 -S 20 -r 640x480 ' + os.path.dirname(os.path.abspath(__file__)) +  '/image.jpg', shell=True, check=True)

        except subprocess.CalledProcessError as e:
                print(e)

        else:
                // 5. 保存したファイルを読み込む。
                // from PIL import Imageが必要。

                photo = Image.open(os.path.dirname(os.path.abspath(__file__)) + '/image.jpg');
                image_io = io.BytesIO()
                photo.save(image_io, format='JPEG')

                image_io.seek(0)

        try:

                // 6. twythonを使ってtweetする。

                formattedDateTime = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
                image_ids = twitter.upload_media(media=image_io)
                twitter.update_status(status="Captured at {time}".format(time=datetime.now().strftime("%Y/%m/%d %H:%M:%S")), media_ids=[image_ids['media_id']])

        except TwythonError as e:
                print(e)


if __name__ == '__main__':
        tweet()
```

## ハマったポイント

### 読み込んだ画像を、upload_media()に指定すると失敗する。

キャプチャまではできていて、Tweetするタイミングで400 (Bad Request)が返却され失敗した。

```
(twitterSample) raspberrypi% python twitterSample.py

--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
--- Capturing frame...
Skipping 20 frames...
Capturing 1 frames...
Captured 21 frames in 0.80 seconds. (26 fps)
--- Processing captured image...
Writing JPEG image to '/home/pi/code/git/twitterSample/image.jpg'.
Twitter API returned a 400 (Bad Request), An error occurred processing your request.
```
コードはtwythonのサンプルコードを元に書いていたので、
サンプルコードを見直すと以下の注意書きを発見。 これに気づいた時はサンプルよく見ろよーとツッコミ。

```
# If you do not seek(0), the image will be at the end of the file and
# unable to be read
image_io.seek(0)
```
[Posting a Status with an Editing Image](https://twython.readthedocs.io/en/latest/usage/advanced_usage.html#posting-a-status-with-an-editing-image)

### python3で書いているとそんなモジュール無いYOと怒られる

ここは仕方がないので、出てきたエラーをコピーして "python3 出てきたエラー"で検索して都度置き換え。

# コードの置き場所とbotの紹介
[Code](https://github.com/hondasports/Bun-chan-Bot.git)
[Twitter](https://twitter.com/Bun_chan_Bot)