google-apps-scriptgoogle-sheetsgoogle-apis-explorergoogle-photos-api

How to use Google Photos API Method: mediaItems.search in Google apps script for a spreadsheet


I really tried to figure this out on my own...

I am trying to load photo metadata from google photos into a sheet using the Google Photos API and google apps script.

I was able to make some progress after a lot of help on a previous question

Is it possible to load google photos metadata into google sheets?

I now have two functions.

function photoAPI_ListPhotos() - Uses Method: mediaItems.list and gives me all my photos that are not archived function photoAPI_ListAlbums() - Uses Method: albums.list and gives me all my albums

What I want to do is retrieve all photos from a specific album. Method: mediaItems.search should do this but it uses the POST protocol and the previous working examples I found only use GET. Looking at the examples available on that page, there is a javascript portion but it does not work in apps script.

The documentation for UrlFetchApp tells me how to format a POST request but not how to add the parameters for authentication.

The external APIs also is not giving me the examples I am looking for.

I feel like I'm missing some essential tiny piece of info and I hope I'm not wasting everyone's time asking it here. Just a solid example of how to use POST with oauth in apps script should get me where I need to go.

Here is my working function for listing all non-archived photos.

function photoAPI_ListPhotos() {
  /*
  This function retrieves all photos from your personal google photos account and lists each one with the Filename, Caption, Create time (formatted for Sheet), Width, Height, and URL in a new sheet.
  it will not include archived photos which can be confusing if you happen to have a large chunk of archived photos some pages may return only a next page token with no media items.

  Requires Oauth scopes. Add the below line to appsscript.json
  "oauthScopes": ["https://www.googleapis.com/auth/spreadsheets.currentonly", "https://www.googleapis.com/auth/photoslibrary", "https://www.googleapis.com/auth/photoslibrary.readonly", "https://www.googleapis.com/auth/script.external_request"]

  Also requires a standard GCP project with the appropriate Photo APIs enabled.
  https://developers.google.com/apps-script/guides/cloud-platform-projects
  */

  //Get the spreadsheet object
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  //Check for presence of target sheet, if it does not exist, create one.
  var photos_sh = ss.getSheetByName("photos") || ss.insertSheet("photos", ss.getSheets().length); 
  //Make sure the target sheet is empty
  photos_sh.clear();
  var narray = []; 

  //Build the request string. Max page size is 100. set to max for speed.
  var api = "https://photoslibrary.googleapis.com/v1/mediaItems?pageSize=100";
  var headers = { "Authorization": "Bearer " +  ScriptApp.getOAuthToken() };
  var options = { "headers": headers, "method" : "GET", "muteHttpExceptions": true };

  //This variable is used if you want to resume the scrape at some page other than the start. This is needed if you have more than 40,000 photos.
  //Uncomment the line below and add the next page token for where you want to start in the quotes.
  //var nexttoken="";

  var param= "", nexttoken;
  //Start counting how many pages have been processed.
  var pagecount=0;

  //Make the first row a title row
  var data = [
    "Filename",
    "description",
    "Create Time",
    "Width",
    "Height",
    "ID",
    "URL",
    "NextPage"
  ];
  narray.push(data);

  //Loop through JSON results until a nextPageToken is not returned indicating end of data
  do {
    //If there is a nextpagetoken, add it to the end of the request string
    if (nexttoken)
      param = "&pageToken=" + nexttoken; 

    //Get data and load it into a JSON object
    var response = UrlFetchApp.fetch(api + param, options);
    var json = JSON.parse(response.getContentText());

    //Check if there are mediaItems to process.
    if (typeof json.mediaItems === 'undefined') {
      //If there are no mediaItems, Add a blank line in the sheet with the returned nextpagetoken

      //var data = ["","","","","","","",json.nextPageToken];
      //narray.push(data);
    } else {
      //Loop through the JSON object adding desired data to the spreadsheet.
      json.mediaItems.forEach(function (MediaItem) {

        //Check if the mediaitem has a description (caption) and make that cell blank if it is not present.
        if(typeof MediaItem.description === 'undefined') {
            var description = "";
          } else {
            var description = MediaItem.description;
          }

        //Format the create date as appropriate for spreadsheets.
        var d = new Date(MediaItem.mediaMetadata.creationTime);

        var data = [
          MediaItem.filename,
          "'"+description, //The prepended apostrophe makes captions that are dates or numbers save in the sheet as a string. 
          d,
          MediaItem.mediaMetadata.width,
          MediaItem.mediaMetadata.height,
          MediaItem.id,
          MediaItem.productUrl,
          json.nextPageToken
        ];
        narray.push(data);
      });
    }

    //Get the nextPageToken
    nexttoken = json.nextPageToken;    

    pagecount++;
    //Continue if the nextPageToaken is not null
    //Also stop if you reach 400 pages processed, this prevents the script from timing out. You will need to resume manually using the nexttoken variable above.
  } while (pagecount<4 && nexttoken);

    //Continue if the nextPageToaken is not null (This is commented out as an alternative and can be used if you have a small enough collection it will not time out.)
  //} while (nexttoken);

  //Save all the data to the spreadsheet.
  photos_sh.getRange(1, 1, narray.length, narray[0].length).setValues(narray);
}

Solution

  • If my understanding is correct, how about this sample script? Please think of this as just one of several answers.

    Sample script 1:

    var albumId = "###"; // Please set the album ID.
    
    var headers = {"Authorization": "Bearer " + ScriptApp.getOAuthToken()};
    var url = "https://photoslibrary.googleapis.com/v1/mediaItems:search";
    var mediaItems = [];
    var pageToken = "";
    do {
      var params = {
        method: "post",
        headers: headers,
        contentType: "application/json",
        payload: JSON.stringify({albumId: albumId, pageSize: 100, pageToken: pageToken}),
      }
      var res = UrlFetchApp.fetch(url, params);
      var obj = JSON.parse(res.getContentText());
      Array.prototype.push.apply(mediaItems, obj.mediaItems);
      pageToken = obj.nextPageToken || "";
    } while (pageToken);
    Logger.log(mediaItems)
    

    Sample script 2:

    When your script is modified, how about the following modified script?

    function photoAPI_ListPhotos() {
      var albumId = "###"; // Please set the album ID.
    
      var ss = SpreadsheetApp.getActiveSpreadsheet();
      var photos_sh = ss.getSheetByName("photos") || ss.insertSheet("photos", ss.getSheets().length); 
      photos_sh.clear();
      var narray = []; 
      var api = "https://photoslibrary.googleapis.com/v1/mediaItems:search";
      var headers = { "Authorization": "Bearer " +  ScriptApp.getOAuthToken() };
      var nexttoken = "";
      var pagecount = 0;
      var data = ["Filename","description","Create Time","Width","Height","ID","URL","NextPage"];
      narray.push(data);
      do {
        var options = {
          method: "post",
          headers: headers,
          contentType: "application/json",
          payload: JSON.stringify({albumId: albumId, pageSize: 100, pageToken: nexttoken}),
        }
        var response = UrlFetchApp.fetch(api, options);
        var json = JSON.parse(response.getContentText());
        if (typeof json.mediaItems === 'undefined') {
          //If there are no mediaItems, Add a blank line in the sheet with the returned nextpagetoken
    
          //var data = ["","","","","","","",json.nextPageToken];
          //narray.push(data);
        } else {
          json.mediaItems.forEach(function (MediaItem) {
            if(typeof MediaItem.description === 'undefined') {
                var description = "";
              } else {
                var description = MediaItem.description;
              }
            var d = new Date(MediaItem.mediaMetadata.creationTime);
            var data = [
              MediaItem.filename,
              "'"+description,
              d,
              MediaItem.mediaMetadata.width,
              MediaItem.mediaMetadata.height,
              MediaItem.id,
              MediaItem.productUrl,
              json.nextPageToken
            ];
            narray.push(data);
          });
        }
        nexttoken = json.nextPageToken || "";
        pagecount++;
      } while (pagecount<4 && nexttoken);
      photos_sh.getRange(1, 1, narray.length, narray[0].length).setValues(narray);
    }
    

    Note:

    Reference:

    If I misunderstood your question and this was not the result you want, I apologize.