自從「Google 關閉 Picasa API」之後,比較適合拿來存放圖片的免費空間變成「Google 相簿」。不過Google 為了不想被當成免費的外連圖床,Google Photo API 有不少限制,可參考這篇新聞「Google相片庫API正式上線,讓開發者在自家應用加入相簿功能」,裡面有提到:
意思就是 API 取得的圖片外連沒有用處,一段時間後就會失效,這件事在「讓 Google Photo 實現相簿畫廊效果」也有提到。
所以 Google Photo API 主要的實質用途,在於上傳、搜尋圖片,至少可以把其他地方的圖片搬過來存放,不需要付費且嚴格來說沒有容量限制。
但必須說 Google Photo API 很不好操作,之前處理「Gmail API」時已確立是最困難的 API 不會被超越,而此次使用 Google Photo API 也被整得灰頭土臉,榮登第二名的地位。本篇將說明在 Google Apps Script(簡稱 GAS)環境操作 API,如何上傳圖片到指定相簿的流程。
一開始可先參考官網文件,瞭解 API 做些什麼事、如何開始:
都是英文可能很吃力,大致說明整個流程要做哪些事:
OAuth 驗證的流程很長,包括「啟用 API、取得 Client ID、密鑰(Client Secret)、永久金鑰(refresh token)」,但取得 OAuth 並非本篇重點,為避免篇幅太長這部分將會簡單帶過,請參考以下參考資料,重點在於想辦法取得可永久使用的 refresh token:
如果是前端工程師的話,我相信有辦法可以搞定 OAuth、refresh token。真的沒辦法的話,有此需求的讀者請再留言反應,視需求程度再來寫 Google API 處理 Oauth 的通用流程。
前面說過了,必須使用 API 建立的相簿才能上傳圖片,所以首先請學習如何操作 API 建立相簿。參考官方文件:
此頁面有語法範例,也提醒前面走 OAuth 驗證時 scope 要至少有photoslibrary 才有寫入的權限。
非常棒的是,這個頁面就有控制台可以操作測試 API,往下捲就會看到:
但是別被騙了,這裡是一個大坑,此處產生的「相簿 ID」一點作用都沒有,根本無法上傳圖片,原因在於這個相簿不是由我們的 APP 所產生,而是由控制台產生,所以我們沒有寫入的權限!
所以之後得乖乖自己寫 code 來建立相簿,這樣產生的相簿 ID 才能用於上傳圖片。不過補充說明,把上圖「Google OAuth 2.0」、「API Key」都取消勾選,打開「Show standard parameters」,填入我們自己的 access_token,這樣就是由我們自己 APP 所建立的相簿,可上傳圖片。
參考官方文件:
這裡詳細說明前面提到的「兩階段上傳」流程,以及語法範例、注意事項。大概整理一下要點:
官方很貼心的提供了控制台可操作,下面這個頁面捲到底就會看到:
但其實這功能只做了一半,因為只能跑第二階段的功能,也就是你要先自己寫 code 上傳完圖片,取得「upload-token」後,才能來這裡測試建立圖片物件的功能,挖勒...
總之官網的文件寫的很詳細,但沒有完整的實例,控制台測試功能也不完整。想要真正測試圖片上傳功能只能真的寫出 code 來才有辦法,那麼就直接來看範例程式碼吧。
因為是在 GAS 環境操作,如果 GAS 不夠熟的話,要跳的坑就更多了,所以先看最大的坑怎麼解再看程式碼。
1. GAS 圖片的 raw bytes
從別處圖床搬到 Google Photo,可用 GAS 的指令 UrlFetchApp.fetch 爬圖片網址。爬回來後要如何轉換格式是一個大問題,究竟要丟什麼格式 Google Photo API 才吃只能不斷測試:
2. GAS 的 payload 格式
一般來說送出 post 的 request body,需要將物件用 JSON.stringify() 轉成字串。但實際使用 GAS 的 UrlFetchApp.fetch 送出 post 時,卻跟 Google Photo API 一直犯衝。
為了排錯,使用官方提供的同樣範例語法,在控制台操作可成功上傳圖片,特地改用前端的 jQuery Ajax 送出也都沒問題,但偏偏 GAS 送出就會報錯。
怎麼找到解法的過程、或原理就先略過了,總之 GAS 的 payload 格式不能用 JSON.stringify() 把 request body 轉換成字串,要直接手動作出 request body 的字串格式,才能被 Google Photo API 吃進去,來跳過這最後一個坑。
3. 範例程式碼
所有重點已用註解標示,以下依流程重點說明:
Google特別提醒了開發者,在呼叫照片內容列表之後,應用程式應該儲存媒體檔案的ID,而非回傳的檔案本身,因為媒體檔案內容可能會有改變,並且在一定時間之後,回應的內容包括URL會過期
意思就是 API 取得的圖片外連沒有用處,一段時間後就會失效,這件事在「讓 Google Photo 實現相簿畫廊效果」也有提到。
所以 Google Photo API 主要的實質用途,在於上傳、搜尋圖片,至少可以把其他地方的圖片搬過來存放,不需要付費且嚴格來說沒有容量限制。
但必須說 Google Photo API 很不好操作,之前處理「Gmail API」時已確立是最困難的 API 不會被超越,而此次使用 Google Photo API 也被整得灰頭土臉,榮登第二名的地位。本篇將說明在 Google Apps Script(簡稱 GAS)環境操作 API,如何上傳圖片到指定相簿的流程。
一、完整流程
一開始可先參考官網文件,瞭解 API 做些什麼事、如何開始:
都是英文可能很吃力,大致說明整個流程要做哪些事:
- 取得 OAuth 驗證:
- 啟用 API、取得 Client ID、密鑰
- 申請永久 token,將來才不用每次重新驗證
- 建立相簿:
- 這一步非常重要,也是遇到的第一個大坑
- 所有在「Google 相簿」官網上建立的相簿都無法用 API 上傳圖片
- 只有用 API 建立的相簿才能上傳圖片
- 上傳圖片:
- 這是第二個坑,Google Photo 很不乾脆,不知為何要分兩個動作,上傳圖片後只能拿到一個 token
- 接著要拿 token 在相簿裡面建立圖片物件,才能完成上傳
- 容易掉坑的地方在於,不管上傳什麼都會拿到 token,即使圖片格式不對這一步都不會報錯。要等到最後一步「建立圖片物件」失敗時,讓你找不出究竟整個過程哪個動作錯誤,才導致無法成功建立物件。
- 也許官方背後的意涵是,只要搞得越複雜,Google Photo 被拿來濫用的機率就越低
- 建立圖片物件:最後一個坑,也是搞最久的一個地方,後面範例程式碼再說明
二、取得 OAuth 2.0 金鑰
OAuth 驗證的流程很長,包括「啟用 API、取得 Client ID、密鑰(Client Secret)、永久金鑰(refresh token)」,但取得 OAuth 並非本篇重點,為避免篇幅太長這部分將會簡單帶過,請參考以下參考資料,重點在於想辦法取得可永久使用的 refresh token:
- 取得 Google API Key(金鑰) 流程,啟用服務:這篇可知道如何啟用 Google Photo API
- Get started with REST:官網文件可知道如何走 OAuth 2.0 的流程,取得 Client ID、密鑰
- Google OAuth 2.0:這篇有 OAuth 2.0 的流程,以及取得 refresh token 的作法
- 使用c#連接google photos api實作:這篇有串接 Google Photo API 及取得 refresh token 的方式,不過 C# 語言可能會看不懂
如果是前端工程師的話,我相信有辦法可以搞定 OAuth、refresh token。真的沒辦法的話,有此需求的讀者請再留言反應,視需求程度再來寫 Google API 處理 Oauth 的通用流程。
三、建立相簿
前面說過了,必須使用 API 建立的相簿才能上傳圖片,所以首先請學習如何操作 API 建立相簿。參考官方文件:
此頁面有語法範例,也提醒前面走 OAuth 驗證時 scope 要至少有
非常棒的是,這個頁面就有控制台可以操作測試 API,往下捲就會看到:
- A:在這裡設定好相簿標題就好
- B:按下 EXECUTE 執行 API
- C:這裡可看到 API 範例語法,將來照抄很方便
- D:執行成功的話,建立相簿後紅色底線這裡會產生「相簿 ID」,將來上傳圖片需要用到此 ID
- 可注意最下方紅框,isWriteable: true 代表這個相簿可寫入,才能上傳圖片
但是別被騙了,這裡是一個大坑,此處產生的「相簿 ID」一點作用都沒有,根本無法上傳圖片,原因在於這個相簿不是由我們的 APP 所產生,而是由控制台產生,所以我們沒有寫入的權限!
所以之後得乖乖自己寫 code 來建立相簿,這樣產生的相簿 ID 才能用於上傳圖片。不過補充說明,把上圖「Google OAuth 2.0」、「API Key」都取消勾選,打開「Show standard parameters」,填入我們自己的 access_token,這樣就是由我們自己 APP 所建立的相簿,可上傳圖片。
四、上傳圖片
參考官方文件:
這裡詳細說明前面提到的「兩階段上傳」流程,以及語法範例、注意事項。大概整理一下要點:
- 一次最多可上傳 50 圖,一張圖片最大 50MB,一個相簿最多裝 20000 張圖
- 上傳的圖片格式必須為「raw bytes」
- 依照 API 格式上傳完畢後,會返回一個字串「upload-token」,要記住這個字串
- 依照 API 第二階段的格式,需要設定「相簿 ID」、輸入「upload-token」,有幸成功的話,這張圖片就會出現在相簿裡了
- API 第二階段有個參數「albumPosition」可設定上傳圖片的位置,例如強制擺在相簿最前面,但我怎麼做都是出現在最後面,所以我認為這參數是裝飾用的。
五、建立圖片物件
官方很貼心的提供了控制台可操作,下面這個頁面捲到底就會看到:
但其實這功能只做了一半,因為只能跑第二階段的功能,也就是你要先自己寫 code 上傳完圖片,取得「upload-token」後,才能來這裡測試建立圖片物件的功能,挖勒...
總之官網的文件寫的很詳細,但沒有完整的實例,控制台測試功能也不完整。想要真正測試圖片上傳功能只能真的寫出 code 來才有辦法,那麼就直接來看範例程式碼吧。
六、GAS 範例程式碼
因為是在 GAS 環境操作,如果 GAS 不夠熟的話,要跳的坑就更多了,所以先看最大的坑怎麼解再看程式碼。
1. GAS 圖片的 raw bytes
從別處圖床搬到 Google Photo,可用 GAS 的指令 UrlFetchApp.fetch 爬圖片網址。爬回來後要如何轉換格式是一個大問題,究竟要丟什麼格式 Google Photo API 才吃只能不斷測試:
- UrlFetchApp.fetch(imgUrl).getContentText() → 這是文字資料,不可以
- UrlFetchApp.fetch(imgUrl).getContent() → 官方說明書說可返回 raw binary content → 實測 ok
- UrlFetchApp.fetch(imgUrl).getBlob().getBytes() → 先取 blob 物件再轉 byte 格式 → 實測 ok
2. GAS 的 payload 格式
一般來說送出 post 的 request body,需要將物件用 JSON.stringify() 轉成字串。但實際使用 GAS 的 UrlFetchApp.fetch 送出 post 時,卻跟 Google Photo API 一直犯衝。
為了排錯,使用官方提供的同樣範例語法,在控制台操作可成功上傳圖片,特地改用前端的 jQuery Ajax 送出也都沒問題,但偏偏 GAS 送出就會報錯。
怎麼找到解法的過程、或原理就先略過了,總之 GAS 的 payload 格式不能用 JSON.stringify() 把 request body 轉換成字串,要直接手動作出 request body 的字串格式,才能被 Google Photo API 吃進去,來跳過這最後一個坑。
3. 範例程式碼
var photo_client_id = "xxxxxxxxxxxxxxxxxxxxx", // 填入自己的 Client ID
photo_client_secret = "xxxxxxxxxxxxxxxxxxxxx", // 填入自己的 Client Secret
photo_refresh_token = "xxxxxxxxxxxxxxxxxxxxx", // 填入自己的 refresh_token
albumId = "xxxxxxxxxxxxxxxxxxxxx", // 填入自己的相簿 ID
albumTitle = "WFU BLOG 測試", // 填入相簿描述
fileName = "WFUBLOG.jpg", // 填入圖片檔名
access_token = getAccessToken();
function uploadPhoto() {
var src = "https://1.bp.blogspot.com/-vhIWukZmniI/VA3My_ptRfI/AAAAAAAAKM0/DaF4uGRxB0Q/s200/wfublog-logo-8abeb7.png", // 這裡是上傳的圖片網址
uploadToken = getUploadToken(src),
uploadUrl = "https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate",
data = '{"albumId":"' + albumId + '","newMediaItems": [{"description": "test","simpleMediaItem": {"fileName":' + fileName + ',"uploadToken":"' + uploadToken + '"}}], "albumPosition":{"position": "FIRST_IN_ALBUM"}}', // 若用 JSON.stringify(data) → google photo api 吃不到,自行轉成字串最保險
options = {
method: "post",
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "application/json"
},
payload: data,
muteHttpExceptions: true
},
response = UrlFetchApp.fetch(uploadUrl, options);
Logger.log(response);
}
function getUploadToken(src) {
var rawData = getPhotoRaw(src),
options = {
"method": "post",
"headers": {
"Authorization": "Bearer " + access_token
},
"Content-type": "application/octet-stream",
"payload": rawData,
"X-Goog-Upload-File-Name": fileName,
"X-Goog-Upload-Protocol": "raw"
},
uploadRawUrl = "https://photoslibrary.googleapis.com/v1/uploads",
uploadToken = UrlFetchApp.fetch(uploadRawUrl, options);
return uploadToken;
}
function getPhotoRaw(src) {
var response = UrlFetchApp.fetch(src);
return response.getContent(); // 圖片轉成 raw binary 格式
}
function createAlbum() {
var album = {
album: {
title: albumTitle
}
},
url = "https://photoslibrary.googleapis.com/v1/albums",
options = {
"method": "post",
"headers": {
"Authorization": "Bearer " + access_token
},
"Content-Type": "application/json",
"payload": JSON.stringify(album)
},
response = UrlFetchApp.fetch(url, options);
Logger.log(JSON.stringify(response)); // 取得相簿 ID
}
function getAccessToken() {
var options = {
method: "post"
},
fetchUrl = "https://www.googleapis.com/oauth2/v3/token?client_id=" + photo_client_id + "&client_secret=" + photo_client_secret + "&refresh_token=" + photo_refresh_token + "&grant_type=refresh_token",
response = UrlFetchApp.fetch(fetchUrl, options),
results = JSON.parse(response.getContentText()),
access_token = results.access_token;
return access_token;
}
所有重點已用註解標示,以下依流程重點說明:
- 先填入自己的 photo_client_id、photo_client_secret、photo_refresh_token,以及 albumTitle、fileName
- 接著執行 createAlbum() 建立相簿,記下返回的相簿 ID,填入 albumId
- 在 uploadPhoto 裡面可替換要上傳的圖片網址,接著執行 uploadPhoto() 就可上傳圖片到指定相簿
更多 Google Apps Script 相關技巧:
更多 Google Photo 相關文章: