ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

続・RailsアプリからGoogleフォトに写真をアップロードしてみる

暑い!Webエンジニアの本間です。

早速で恐縮なのですが、半年ほど前「RailsからGoogleフォトに写真をアップロードしてみる」というエントリーを書きました。

tech.unifa-e.com

こちらの記事では、PicasaのAPIを使って写真をアップロードしていました。 やりたいことは一通りできたのですが、記事の後半でも書いた通り、いつまでサポートされるかわからない問題がありました。

その後、2018年5月8日Googleから Google Phtos API の発表がありました。 正式なGoogleフォト専用のAPIということで、今後はこちらのAPIを使う方がよさそうです。 今回こちらのAPIを使って、前回作成したアプリケーションの改修を行い、使い勝手などを確認しようと思います。

作成するアプリケーション

  • 内容は前回と同じ
  • 使用するAPIをPicasa Web APIからGoogle Photos APIに変更

f:id:ryu39:20180129172225p:plain

サンプルコード

前回と同じリポジトリに今回の修正を反映しています。

bitbucket.org

動かし方等はREADME.mdを参照してください。

動作確認

前回と同じくherokuでお試し環境も作っています。

https://frozen-citadel-57077.herokuapp.com/

準備など

  • Google Developers Console でプロジェクトの作成、OAuth用のCredentialを登録するところは同じ
  • Google Photos APIはデフォルトで有効化されていないので、有効化する必要があり
  • 写真一覧画面に関しては、前回と全く同じなので割愛

Googleフォトへ写真をアップロード

Google Photos APIの概要

ドキュメントベースですが、Google Photos APIの概要 を確認しておきます。

  • RESTにそったURI
  • リクエストとレスポンスのBodyの書式がJSON(前はXML)
  • 「ライブラリ」「アルバム」「メディア」「共有」という概念が整理された
  • 現時点(2018年7月18日)では「Developer Preview」で公開中。そのため、APIの呼び出し可能回数が低め(1日2500回まで)に設定されている。
  • アップロードされる写真は、容量無制限の「高画質」ではなく、容量制限のある「元の画質」でアップロードされる模様 https://developers.google.com/photos/library/guides/api-limits-quotas#photo-storage-quality 。ここは、今後のAPIの改善でアップロード画質を選択できるようにしてほしい
  • パートナープログラム がある。APIの呼び出し回数の上限をあげる場合や、ユーザーの写真を商用利用する場合、参加が必要な模様。

アクセストークンの取得

この部分は前回とほとんど一緒なので割愛します。詳細は 前の記事 を参照ください。

一点だけ注意があります。認可画面へリダイレクトする際に指定する scope の値が変更されており、細かい制御が可能になっています。 scopeの一覧 今回必要なのは写真のアップロードだけなので、 https://www.googleapis.com/auth/photoslibrary.appendonly を指定します。

認可の画面はこんな感じでした。

f:id:ryu39:20180718122255p:plain

写真のアップロード

アップロード手順を記載したガイド があるので、これに従います。 写真のアップロードのAPI呼び出しは2段階に分けられています。

f:id:ryu39:20180718121529p:plain

1の写真アップロード(uploads)は、こんな感じのコードです。 photo はActiveRecordオブジェクトです。画像ファイルをActiveStorageで1つ保持しています。セッションにAPIを呼び出すためのアクセストークンが格納されています。

def upload_image(photo)
  uri = ::URI.parse('https://photoslibrary.googleapis.com/v1/uploads')

  req = ::Net::HTTP::Post.new(uri)
  req['Authorization'] = "Bearer #{session[:access_token]}"
  req['Content-Type'] = 'application/octet-stream'
  req['X-Goog-Upload-File-Name'] = photo.file.blob.filename
  req.body = photo.file.blob.download

  res = ::Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    http.request(req)
  end
  res.is_a?(::Net::HTTPSuccess) ? res.body : res.error!
end

このコードを実行すると、BodyにuploadTokenがセットされたレスポンスが返却されます。

2の一括作成(batchCreate)のコードはこんな感じです。1の結果のuploadTokenをリクエストにセットしてPOSTしています。今回は1件だけですが、複数件のuploadTokenをセットすることも可能です。

def create_media_item(upload_token)
  uri = ::URI.parse('https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate')

  req = ::Net::HTTP::Post.new(uri)
  req['Authorization'] = "Bearer #{session[:access_token]}"
  req['Content-Type'] = 'application/json'
  req.body = {
    newMediaItems: [
      {
        description: "Google Photos API test",
        simpleMediaItem: {
          uploadToken: upload_token
        }
      }
    ]
  }.to_json

  res = ::Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    http.request(req)
  end
  res.is_a?(::Net::HTTPSuccess) ? res.body : res.error!
end

以上で無事、Googleフォトに写真をアップロードすることができました。

f:id:ryu39:20180718121735p:plain

(おまけ)Google Photos APIで気になったところ

Google Photos APIの中で RESTでは表現できないアクションリソース名:動詞 で表現している箇所がありました。 今回使ったAPIの中では、メディアの一括作成のURIが https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate となっています。

通常は https://photoslibrary.googleapis.com/v1/mediaItems/batchCreate とかで : ではなく / で区切ると思うんですが、 batchCreate の部分が動詞なのでリソースになっておらず、個人的にかなり違和感がありました。 gRPCを使えば良いという声も聞こえてきそうですが、RESTでJSONベースのAPIを維持する場合での一つの解決作を見た気がします。

もちろん、 POST https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate というのは、やっぱ違和感は残りますが、 / で区切るよりは少し緩和された印象です。 URIの中で : って直接使ってよいのかはちょっと怪しい部分ですが、URI設計に頭を悩ますぐらいであれば、解決策の1つとしてはありだなーと思いました。

まとめ

今回、新しく公開されたGoogle Photos APIを使ってrailsから写真をアップロードしてみました。 写真のアップロード以外にも、アルバムや写真の一覧の取得や、公開に関する設定もできそうです。 過去試したPicasaのAPIより洗練されており、今後の拡張、サポートも十分期待できそうなため、Googleフォトとの連携が今後広まっていきそうだなと感じております。

最後までご覧いただきありがとうございました。