Google Photos APIsでフォトアルバム

私は普段Android端末を使っており、奥さんと旅行などに行った際よく写真を撮ります。
アカウントの同期を行っている方は、Android端末で撮った写真が自動でGoogleのPhotosサーバにバックアップされると思います。
[Google Photos APIs](https://developers.google.com/photos)が公開され、Google Photosサーバから直接画像を取得出来るようになったため、これを利用してフォトアルバムを作ろうと思ったのが本記事のきっかけです。

## はじめに
本記事では、[Google Photos APIs](https://developers.google.com/photos)を用いフォトアルバムを表示するWebアプリ(Spring-boot)を作成します。
ソースは、[Github](https://github.com/RioAkiyama/KarionPhoto) にあげております。
下図の様に指定期間範囲内に撮った画像を、指定の間隔で再生するよう実装しております。
![sample1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463879/d691d77f-6527-6803-253c-94d30637c3a8.png)
### スコープ
・Google Photos APIsの有効化
・認証情報の取得
・フォトアルバム Webアプリ開発

## Google Photos APIsの有効化
公式の案内 [Enable the API](https://developers.google.com/photos/library/guides/get-started#enable-the-api) に従えば、3step程で有効化できます。

## 認証情報の取得
こちらも公式の案内 [Request an OAuth 2.0 client ID](https://developers.google.com/photos/library/guides/get-started#request-id) が分かりやすく、こちらを参照すれば簡単に出来ます。
サンプルのソースでは、下記設定としております。
[Name]: OAuth client
[Authorized JavaScript origins]： http://localhost:8081
[Authorized redirect URIs]： http://localhost:8081/Callback

設定が完了したら、作成したOAuth 2.0 Client IDsのダウンロードボタンから認証ファイルを取得します。
Webサーバが[Google Photos APIs](https://developers.google.com/photos)を使用する際に必要となります。

## フォトアルバム Webアプリ開発
### 認証
```Java:PhotosLibraryClientFactory.java
public static PhotosLibraryClient Client;

// ユーザーに手動で認証してもらうURL
public static String AuthUrl;

// [本記事]-[認証情報の取得]でダウンロードした認証ファイルのパス
public String mCredentialsPath;

public PhotosLibraryClientFactory(String credentialsPath) {
	this.mCredentialsPath = credentialsPath;
}

public boolean init() throws IOException, GeneralSecurityException {
	if(Client != null) return true;
	
	Credentials credentials = getUserCredentials();
	if(credentials == null) return false;
	
	PhotosLibrarySettings settings = PhotosLibrarySettings.newBuilder()
			.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
			.build();
	
	Client = PhotosLibraryClient.initialize(settings);
	return true;
}
	
private Credentials getUserCredentials() throws IOException, GeneralSecurityException {
	GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(new FileInputStream(this.mCredentialsPath)));
	String clientId = clientSecrets.getDetails().getClientId();
	String clientSecret = clientSecrets.getDetails().getClientSecret();
	
	GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
		GoogleNetHttpTransport.newTrustedTransport(),
		JSON_FACTORY,
		clientSecrets,
		ImmutableList.of("https://www.googleapis.com/auth/photoslibrary.readonly")
	)
		.setDataStoreFactory(new FileDataStoreFactory(DATA_STORE_DIR))
		.setAccessType("offline")
		.build();
	
	LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(LOCAL_RECEIVER_PORT).build();
	
	Credential credential = flow.loadCredential("user");
	if (credential != null && (
			credential.getRefreshToken() != null ||
			credential.getExpiresInSeconds() == null ||
			credential.getExpiresInSeconds() > 60)) {
		// 認証成功した場合は認証情報を返す
		return UserCredentials.newBuilder()
				.setClientId(clientId)
				.setClientSecret(clientSecret)
				.setRefreshToken(credential.getRefreshToken())
				.build();
	}
	
	// 手動で認証が必要な場合は、認証URLを取得する
	String redirectUri = receiver.getRedirectUri();
	AuthorizationCodeRequestUrl authorizationUrl = flow.newAuthorizationUrl().setRedirectUri(redirectUri);
	AuthUrl = authorizationUrl.build();
	return null;
}
```

手動での認証が必要な場合は、AuthUrl に認証URLを設定しています。
こちらはUI側で一度開き、ユーザーに手作業で認証作業を行ってもらう必要があります。

### 画像のURL取得

```Java:PhotosLibraryClientFactory.java
public static java.util.Date StartDate;
public static java.util.Date EndDate;

public Iterable<MediaItem> getPhotos() throws IOException, GeneralSecurityException {
	if(!init()) return null;
	
	Builder builder = SearchMediaItemsRequest.newBuilder();
	if(StartDate != null && EndDate != null) {
		DateFilter dateFilter = DateFilter.newBuilder().addRanges(DateRange.newBuilder()
						.setStartDate(CommonUtils.convertToGoogleDate(StartDate))
						.setEndDate(CommonUtils.convertToGoogleDate(EndDate)).build()).build();
		Filters filter = Filters.newBuilder().setDateFilter(dateFilter).build();
		builder.setFilters(filter);
	}
	
	SearchMediaItemsRequest request = builder.build();
	SearchMediaItemsResponse response = PhotosLibraryClientFactory.Client.searchMediaItemsCallable().call(request);
	return response.getMediaItemsList();
}
```
StartDateやEndDateが指定されていた場合、指定期間に撮った写真を検索します。
指定されていない場合は検索条件なしになりますが「このアルバムの画像を取得」のような検索の仕方も可能ですので、お好みの検索ロジックを組むと良いと思います。