何ができるの?
VRChatのワールドに置いたフォトフレームに、Google Photosで指定したアルバム内の画像を表示できるようになります。
仕組みとしてはこちら(https://qiita.com/nyakome306/items/bab82e741dd66053b374 )を基に、VRCPanoramaでGoogle Drive上の画像を参照・表示し、Google Apps Scriptで定期的にGoogle Photosの画像を取得、Driveの画像を更新する形となります。
この記事関係なくGoogleの「バックアップと同期」をPCにインストールしておくと
撮ったSSが自動でGoogle PhotosにアップロードされてスマホからSSを管理できて捗るのでお勧め。
この記事では、一定時間で表示されるSSが切り替わるフォトフレームの実装について紹介します。
Google Apps Script側
一部箇所の説明を省いてますので、こちらも参照しながら設定してください
https://qiita.com/nyakome306/items/bab82e741dd66053b374
スクリプトを新規作成
以下のURLにアクセスし、左上の新規スクリプトをクリックするとエディタが開きます
https://script.google.com
Google Cloud Platform(GCP)プロジェクトと連結
以下のURLにアクセスし、左上の▼をクリックして新しいプロジェクトを作成します。
https://console.cloud.google.com
プロジェクトのダッシュボードを表示し、左側にあるプロジェクトIDを控えておきます。
先ほど作成したスクリプトのエディタ画面よりリソース→Cloud Platformプロジェクトを選択、プロジェクトIDを入力して”プロジェクトを設定”をクリックします。
Photos Library APIキーを取得
GCPのプロジェクトのダッシュボードの上側の検索窓にPhotos Library APIと入力して検索し、画像の通りにクリックしていきます。
名前はそのままでも大丈夫です。
表示されたクライアントID、クライアントシークレットを控えます。
コード本体
以下コードを新規作成したスクリプトに貼り付けてください。
またクライアントIDとクライアントシークレットを上で入手したものに書き換え、保存ボタン💾をクリックしてください。
表示する写真の枚数はphotoNumで指定できます。
表示するアルバムの選択は、photoalbum()を実行すると表示→ログにアルバム名とアルバムIDが出力されるため、表示したいアルバムのIDをコピーし、スクリプトのalbumidを書き換えてください。
var clientid = "クライアントID";
var clientsecret = "クライアントシークレット";
//表示枚数
var photoNum = 10;
//表示するアルバムID
var albumid = "表示したいアルバムのID";
var tokenurl = "https://accounts.google.com/o/oauth2/token"
var authurl = "https://accounts.google.com/o/oauth2/auth"
var scope = "https://www.googleapis.com/auth/photoslibrary"
//アクセストークンURLを含んだHTMLを返す関数
function authpage(){
var service = checkOAuth();
var authorizationUrl = service.getAuthorizationUrl();
Logger.log(authorizationUrl);
}
//認証チェック
function checkOAuth() {
return OAuth2.createService("PhotosAPI")
.setAuthorizationBaseUrl(authurl)
.setTokenUrl(tokenurl)
.setClientId(clientid)
.setClientSecret(clientsecret)
.setCallbackFunction("authCallback") //認証を受けたら受け取る関数を指定する
.setPropertyStore(PropertiesService.getScriptProperties()) //スクリプトプロパティに保存する
.setScope(scope)
.setParam('login_hint', Session.getActiveUser().getEmail())
.setParam('access_type', 'offline')
.setParam('approval_prompt', 'force');
}
//認証コールバック
function authCallback(request) {
var service = checkOAuth();
Logger.log(request);
var isAuthorized = service.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput("認証に成功しました。ページを閉じてください。");
} else {
return HtmlService.createHtmlOutput("認証に失敗しました。");
}
}
//ログアウト
function reset() {
checkOAuth().reset();
}
//アルバム一覧を取得する
function photoalbum(){
var service = checkOAuth();
var accessToken = service.getAccessToken();
var url = "https://photoslibrary.googleapis.com/v1/albums";
var response = UrlFetchApp.fetch(url, {
method: 'GET',
headers: {
Authorization: 'Bearer ' + accessToken
},
contentType: "application/json",
muteHttpExceptions: true
});
//一覧データを取得する
var result = JSON.parse(response.getContentText())["albums"];
for(i=0;i<result.length;i++){
Logger.log(result[i]["title"]+","+result[i]["coverPhotoMediaItemId"]);
}
}
//アルバム内の画像一覧取得
function albumPhotolist(albumId){
var service = checkOAuth();
var accessToken = service.getAccessToken();
//アルバムIDの指定など
var pageSize = photoNum + 10;
var payload = {
"pageSize":pageSize.toString(),
"albumId": albumId,
};
var url = "https://photoslibrary.googleapis.com/v1/mediaItems:search";
var response = UrlFetchApp.fetch(url, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + accessToken
},
contentType: "application/json",
payload : JSON.stringify(payload),
muteHttpExceptions: true
});
//画像Urlを取得
var result = JSON.parse(response.getContentText())["mediaItems"];
var photolist = result.filter(function(v) {return v.mimeType == "image/png" || v.mimeType == "image/jpeg"});
for(i=0;i<photolist.length;i++){
Logger.log(photolist[i].baseUrl);
}
return photolist;
}
//初期化
function photoFilesInit(){
var albumFolders=DriveApp.getFoldersByName("VRCPhotoAlbum");
var albumFolder;
if(!albumFolders.hasNext()){
albumFolder = DriveApp.createFolder("VRCPhotoAlbum");
}else{
albumFolder = albumFolders.next();
}
setProp("folderId",albumFolder.getId());
var strImURL = "Image URL list";
for(i=0;i<photoNum;i++){
var photo;
var photos = DriveApp.getFilesByName("photo"+i+".png");
if(!photos.hasNext()){
photo = albumFolder.createFile("photo"+i+".png", "", MimeType.PNG)
}else{
photo = photos.next();
}
var access = photo.getSharingAccess()
//公開範囲の設定
if (access != DriveApp.Access.ANYONE) photo.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
strImURL += "\nhttp://drive.google.com/uc?export=view&id="+photo.getId();
setProp("photo"+i+"Id",photo.getId());
}
Logger.log(strImURL);
}
//定期実行関数
function updatePhotoFiles(){
//albumIdに指定したアルバムを表示する
var photoUrls = albumPhotolist();
for(i=0;i<photoNum;i++){
var blob = UrlFetchApp.fetch(photoUrls[i]["baseUrl"]+"=d").getBlob();
updateFile("photo"+i+".png",getProp("photo"+i+"Id"),blob);
}
}
function setProp(key,val){
PropertiesService.getScriptProperties().setProperty(key,val);
}
function getProp(key){
var value = PropertiesService.getScriptProperties().getProperty(key);
return value;
}
function updateFile(title,id,blob) {
var body = {
title: title,
mimeType: 'image/png'
};
file = Drive.Files.update(body,id, blob);
}
API有効化
Drive APIを用いるため、リソース→Googleの拡張サービスより選択して有効化してください。
GASエディタ上の関数の選択よりauthpageを選択して実行▶ボタンを押してください。
表示→ログを開き、表示されているURLにアクセスしてください。
Googleアカウントのログイン画面が出るのでログインし、詳細→photos library(安全でないページに移動)→許可とクリックしてください。
同期ファイル生成、動作テスト
GASエディタ上の関数の選択よりphotoFilesInitを選択して実行▶ボタンを押してください。VRCPanoramaより参照されるファイルがGoogleDrive上に作成されます。(VRCPhotoAlbumフォルダ)
またupdatePhotoFilesを実行するとVRCPhotoAlbumフォルダ内の画像が指定したアルバムのものに更新されます。
定期実行
updatePhotoFiles()を編集→現在のプロジェクトのトリガーからトリガーを作成して定期実行するようにしてください。
VRChat側
パッケージの導入
以下の.unitypackageを導入してphotoFrame.prefabをワールドに置いてください。
https://drive.google.com/open?id=1rQ5jNQfXfhINcihF7A1M3TAev2JUORy_
URLの登録
GASエディタ上の関数の選択よりphotoFilesInitを選択して実行▶ボタンを押してからログを開くと、VRCPanoramaより参照される画像ファイルのURLが一覧で表示されます。
設置したphotoFrame→screen内のVRCPanoramaコンポーネントのsizeを上のphotoNumで指定した値(初期値10)にし、追加されたフォームにURLを順に入力してください。
Unityの再生ボタンを押して、screenにSSが表示されれば正しく導入されています。