昔から動かしていた思い出を振り返るためのシステム実装を今時にしました!
機能は同じですが、GooglePhotosAPIのフィルタ機能などで追加の機能も実装予定です!!例えば、以前やりたいな〜と記事に書いていた「人が写っている写真を優先的に表示する」なども可能になります。
作った時の記事
システムの機能概要
- 1時間ごとにその日の日付の写真をランダムでツイートする。
- 例えば2019/09/22ならxxxx/09/22の写真がランダムにツイートされる。
変更点
変更点 | Before | After |
---|---|---|
実行環境 | ラズパイ | AWS Lambda |
写真保存先 | SQLite3 on 外付けHDD | Google Photo |
全部クラウド上で動きます。今時じゃないですか?笑
pythonのコード
- 前提
- error処理などはあまいです。。。
Google Photos API関連のコード
googlePhoto.py
from googleapiclient.discovery import build
import google.oauth2.credentials
import datetime
import random
import requests
import logging
import sys
import os
import time
API_TRY_MAX=3
def getService():
'''
service objectの取得。
'''
env = os.environ
credentials = google.oauth2.credentials.Credentials("dummy",
refresh_token=env['REFRESH_TOKEN'],
# AUTHORIZATION_CODEからTokenを取得したURI
token_uri=env['TOKEN_URI'],
client_id=env['CLIENT_ID'],
client_secret=env['CLIENT_SECRET'])
service = build('photoslibrary', 'v1', credentials=credentials, cache_discovery=False)
return service
def getMediaItems(service):
'''
実行日と日付が同じMediaItemsを全て取得する。
EX) 2019/09/22日に実行した場合は、xxxx/09/22に保存されたMediaItemsを取得する。
'''
today = datetime.date.today()
body = {'pageSize': 100,
# "includedContentCategories"により、PEOPELなどカテゴリでフィルタできる。面白いから機能追加で利用を検討。
# https://developers.google.com/photos/library/reference/rest/v1/mediaItems/search#contentcategory
'filters': {'contentFilter': {"includedContentCategories": ["NONE"]},
'dateFilter': {'dates': [{"year": 0, "month": today.month, "day": today.day}]}}}
mediaItems = []
pagesize = 0
response = {'nextPageToken':'dummy'}
while 'nextPageToken' in response.keys():
# たまにエラーが発生するため、リトライ処理を書く。
for try_num in range(API_TRY_MAX):
try:
response = service.mediaItems().search(body=body).execute()
# responseがエラーなく終了したらfor文を抜ける。
break
except Exception as e:
logging.error(e)
if try_num <= API_TRY_MAX:
time.sleep(3)
else:
logging.error("Max retry out: " + str(API_TRY_MAX))
# エラーでリトライアウトした場合は終了
sys.exit(1)
if 'mediaItems' in response.keys():
pagesize += len(response['mediaItems'])
mediaItems += response['mediaItems']
if 'nextPageToken' in response.keys():
body['pageToken'] = response['nextPageToken']
logging.debug('Get pagesize Sum: ' + str(pagesize))
return mediaItems
def getRandomMediaItemsWithImageBinary(mediaItems):
'''
mediaItemsからランダムに1個選択し、写真をbaseUrlにより取得し'imageBinary'のkeyに追加して返す。
'''
if len(mediaItems) == 0:
logging.debug("A number of MediaItems is 0.")
return None
mediaItem = random.choice(mediaItems)
response = requests.get(mediaItem['baseUrl'])
if response.status_code != 200:
logging.error("Request baseUrl was failed")
sys.exit(1)
mediaItem['imageBinary'] = response.content
return mediaItem
Twitter関連のコード
twitter.py
#!/usr/bin/env python
# coding: utf-8
import json
import sys
import logging
import os
from requests_oauthlib import OAuth1Session
URL_MEDIA = "https://upload.twitter.com/1.1/media/upload.json"
URL_TEXT = "https://api.twitter.com/1.1/statuses/update.json"
def uploadMedia(mediaItem):
# OAuth認証 セッションを開始
env = os.environ
twitter = OAuth1Session(env['TWITTER_CK'],env['TWITTER_CS'],env['TWITTER_AT'],env['TWITTER_AS'])
# 画像投稿
files = {"media": mediaItem['imageBinary']}
req_media = twitter.post(URL_MEDIA, files=files)
# レスポンスを確認
if req_media.status_code != 200:
logging.error("画像アップデート失敗: %s", req_media.text)
sys.exit(1)
# Media ID を取得
media_id = json.loads(req_media.text)['media_id']
logging.debug("Media ID: %d" % media_id)
# Media ID を付加してテキストを投稿
params = {'status': mediaItem['mediaMetadata']['creationTime'], "media_ids": [media_id]}
req_media = twitter.post(URL_TEXT, params=params)
# レスポンスを確認
if req_media.status_code != 200:
print("テキストアップデート失敗: %s", req_media.text)
sys.exit(1)
return
Lambdaのコード
labda_funtion.py
import json
from googlePhoto import getService, getMediaItems, getRandomMediaItemsWithImageBinary
from twitter import uploadMedia
def lambda_handler(event, context):
service=getService()
mediaItems = getMediaItems(service)
mediaItem = getRandomMediaItemsWithImageBinary(mediaItems)
if mediaItem == None:
return {
'statusCode': 200,
'body': json.dumps('Any media does not exit')
}
uploadMedia(mediaItem)
return {
'statusCode': 200,
'body': json.dumps('Uploaded Media')
}
Lambdaの設定
Lambdaを作成
- 関数名
- memoryTweetFromGooglePhoto
- ランタイム
- python3.7
パッケージ化してupload
必要なパッケージをzipにまとめる。
$ pip install --target ./package requests google-api-python-client google-auth requests-oauthlib [~]
Collecting requests
Using cached https://files.pythonhosted.org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/requests-2.22.0-py2.py3-none-any.whl
Collecting google-api-python-client
Using cached https://files.pythonhosted.org/packages/5e/19/9fd511734c0dee8fa3d49f4109c75e7f95d3c31ed76c0e4a93fbba147807/google-api-python-client-1.7.11.tar.gz
Collecting google-auth
Using cached https://files.pythonhosted.org/packages/c5/9b/ed0516cc1f7609fb0217e3057ff4f0f9f3e3ce79a369c6af4a6c5ca25664/google_auth-1.6.3-py2.py3-none-any.whl
Collecting requests-oauthlib
Using cached https://files.pythonhosted.org/packages/c2/e2/9fd03d55ffb70fe51f587f20bcf407a6927eb121de86928b34d162f0b1ac/requests_oauthlib-1.2.0-py2.py3-none-any.whl
Collecting idna<2.9,>=2.5 (from requests)
Using cached https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests)
Using cached https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 (from requests)
Using cached https://files.pythonhosted.org/packages/81/b7/cef47224900ca67078ed6e2db51342796007433ad38329558f56a15255f5/urllib3-1.25.5-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests)
Using cached https://files.pythonhosted.org/packages/18/b0/8146a4f8dd402f60744fa380bc73ca47303cccf8b9190fd16a827281eac2/certifi-2019.9.11-py2.py3-none-any.whl
Collecting httplib2<1dev,>=0.9.2 (from google-api-python-client)
Using cached https://files.pythonhosted.org/packages/60/55/3902b9f33ad9c15abf447ad91b86ef2d0835a1ae78530f1410c115cf8fe3/httplib2-0.13.1-py3-none-any.whl
Collecting google-auth-httplib2>=0.0.3 (from google-api-python-client)
Using cached https://files.pythonhosted.org/packages/33/49/c814d6d438b823441552198f096fcd0377fd6c88714dbed34f1d3c8c4389/google_auth_httplib2-0.0.3-py2.py3-none-any.whl
Collecting six<2dev,>=1.6.1 (from google-api-python-client)
Using cached https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl
Collecting uritemplate<4dev,>=3.0.0 (from google-api-python-client)
Using cached https://files.pythonhosted.org/packages/e5/7d/9d5a640c4f8bf2c8b1afc015e9a9d8de32e13c9016dcc4b0ec03481fb396/uritemplate-3.0.0-py2.py3-none-any.whl
Collecting pyasn1-modules>=0.2.1 (from google-auth)
Using cached https://files.pythonhosted.org/packages/be/70/e5ea8afd6d08a4b99ebfc77bd1845248d56cfcf43d11f9dc324b9580a35c/pyasn1_modules-0.2.6-py2.py3-none-any.whl
Collecting rsa>=3.1.4 (from google-auth)
Using cached https://files.pythonhosted.org/packages/02/e5/38518af393f7c214357079ce67a317307936896e961e35450b70fad2a9cf/rsa-4.0-py2.py3-none-any.whl
Collecting cachetools>=2.0.0 (from google-auth)
Using cached https://files.pythonhosted.org/packages/2f/a6/30b0a0bef12283e83e58c1d6e7b5aabc7acfc4110df81a4471655d33e704/cachetools-3.1.1-py2.py3-none-any.whl
Collecting oauthlib>=3.0.0 (from requests-oauthlib)
Using cached https://files.pythonhosted.org/packages/05/57/ce2e7a8fa7c0afb54a0581b14a65b56e62b5759dbc98e80627142b8a3704/oauthlib-3.1.0-py2.py3-none-any.whl
Collecting pyasn1<0.5.0,>=0.4.6 (from pyasn1-modules>=0.2.1->google-auth)
Using cached https://files.pythonhosted.org/packages/a1/71/8f0d444e3a74e5640a3d5d967c1c6b015da9c655f35b2d308a55d907a517/pyasn1-0.4.7-py2.py3-none-any.whl
Installing collected packages: idna, chardet, urllib3, certifi, requests, httplib2, pyasn1, pyasn1-modules, rsa, six, cachetools, google-auth, google-auth-httplib2, uritemplate, google-api-python-client, oauthlib, requests-oauthlib
Running setup.py install for google-api-python-client ... done
Successfully installed cachetools-3.1.1 certifi-2019.9.11 chardet-3.0.4 google-api-python-client-1.7.11 google-auth-1.6.3 google-auth-httplib2-0.0.3 httplib2-0.13.1 idna-2.8 oauthlib-3.1.0 pyasn1-0.4.7 pyasn1-modules-0.2.6 requests-2.22.0 requests-oauthlib-1.2.0 rsa-4.0 six-1.12.0 uritemplate-3.0.0 urllib3-1.25.5
You are using pip version 19.0.3, however version 19.2.3 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
$ cd package
$ zip -r9 ${OLDPWD}/function.zip .
省略
$ cd ${OLDPWD}
$ zip -g function.zip googlePhoto.py lambda_function.py twitter.py [~/SDropbox/Dropbox/dev/memoryTweetGooglePhoto]
updating: googlePhoto.py (deflated 51%)
updating: lambda_function.py (deflated 54%)
updating: twitter.py (deflated 50%)
$ aws lambda update-function-code --function-name memoryTweetFromGooglePhoto --zip-file fileb://function.zip
省略
Lambdaの環境変数の設定
GooglePhotsAPI
[追記あり] Google Photos APIsでアルバム作成と写真のアップロード - Qiitaを参照し
- CLIENT_ID
- CLIENT_SECRET
- REFRESH_TOKEN
- TOKEN_URI
- AUTHORIZATION_CODEを利用してTokenを取得したURI
- EX) https://www.googleapis.com/oauth2/v4/token
を設定する。
getFirstToken.sh
#!/bin/bash
# 実行例
# source getFirstToken.sh
# 以下記事を参考にCLIENT_IDとCLIENT_SECRETを用意。
# [[追記あり] Google Photos APIsでアルバム作成と写真のアップロード - Qiita](https://qiita.com/zaki-lknr/items/97c363c12ede4c1f25d2)
CLIENT_ID=xxxxxxxx.apps.googleusercontent.com
CLIENT_SECRET=xxxxxxxxx
# 変更不要
REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob
SCOPE=https://www.googleapis.com/auth/photoslibrary.readonly
echo "以下URLにアクセスし、認証を行う。"
echo "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=$SCOPE&access_type=offline"
echo ""
echo "以下コマンドを実行し、AUTHORIZATION_CODEを変数に書き込む。"
echo 'read AUTHORIZATION_CODE'
echo "以下コマンドを実行し、tokenを取得。"
echo 'curl --data "code=$AUTHORIZATION_CODE" --data "client_id=$CLIENT_ID" --data "client_secret=$CLIENT_SECRET" --data "redirect_uri=$REDIRECT_URI" --data "grant_type=authorization_code" --data "access_type=offline" https://www.googleapis.com/oauth2/v4/token'%
Twitter API
twitter周りの設定は過去記事やPython で Twitter API にアクセス - Qiitaを参考に以下の環境変数を設定する。
- TWITTER_CK
- TWITTER_CS
- TWITTER_AT
- TWITTER_AS
ClowdWatchEventsの設定
あとはこの辺を参考にして、定期実行できるようにすればおしまい。
Lambdaを定期実行するためにCloudWatchを設定する - Qiita
まとめ
もともとあったシステムを今時にしました。全部クラウドでできる世界。素晴らしいですね。