1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AWS LambdaでGooglePhotosAPIから思い出の写真をTwitterに投稿

Last updated at Posted at 2019-09-22

昔から動かしていた思い出を振り返るためのシステム実装を今時にしました!
機能は同じですが、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を参照し

を設定する。

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

まとめ

もともとあったシステムを今時にしました。全部クラウドでできる世界。素晴らしいですね。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?