1

I see that we can import (upload) media (photos, videos,...) from Google Drive to Google Photos. My goal is: do this task with code.

I have successfully got a list of photos from Google Drive, and I store their IDs in an Array. I have this code:

  var arrId =
  [
   //Some image id...
  ]; 
  var length = arrId.length;
  var driveApplication = DriveApp;
  for(var i=0;i<length;++i)
  {
    var file = driveApplication.getFileById(arrId[i]);
    //Now I got the file, how can I programmatically import this file to Google Photos
  }

I have searched the docs of Google Script, but I couldn't find any API of Google Photos.

Thanks.

123iamking
  • 2,387
  • 4
  • 36
  • 56

2 Answers2

3
  • You want to upload an image file in Google Drive to Google Photo.
  • You want to achieve this using Google Apps Script.

If my understanding is correct, how about this answer?

In this answer, I would like to achieve your goal using a Google Apps Script library of GPhotoApp. This library has the method for uploading a file in Google Drive to Google Photo.

Usage:

1. Linking Cloud Platform Project to Google Apps Script Project:

About this, you can see the detail flow at here.

2. Install library

Install this library.

  • Library's project key is 1lGrUiaweQjQwVV_QwWuJDJVbCuY2T0BfVphw6VmT85s9LJFntav1wzs9.
IMPORTANT

This library uses V8 runtime. So please enable V8 at the script editor.

About scopes

This library use the following 2 scopes. In this case, when the library is installed, these scopes are automatically installed.

  • https://www.googleapis.com/auth/photoslibrary
  • https://www.googleapis.com/auth/script.external_request

Sample script 1:

At first, please retrieve the album ID you want to upload. For this, please use the following script.

function getAlbumList() {
  const excludeNonAppCreatedData = true;
  const res = GPhotoApp.getAlbumList(excludeNonAppCreatedData);
  console.log(res.map(e => ({title: e.title, id: e.id})))
}

Sample script 2:

Using the retrieved album ID, you can upload the image files to Google Photo. In this sample script, arrId of your script is used.

function uploadMediaItems() {
  const albumId = "###";  // Please set the album ID.
  var arrId =
  [
   //Some image id...
  ]; 

  const items = arrId.map(id => {
    const file = DriveApp.getFileById(id);
    return {blob: file.getBlob(), filename: file.getName()};
  });
  const res = GPhotoApp.uploadMediaItems({albumId: albumId, items: items});
  console.log(res)
}

Note:

  • In my environment, I could confirm that the above scripts worked.

References:

halfer
  • 19,824
  • 17
  • 99
  • 186
Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • 1
    Thanks a lot for your help on this issue. I found that my script wasn't working on V8 yesterday when I tried to answer this question. I had some additional issues since I also wanted to upload the destination album in Google Photo Library and the file name which complicated the issue a lot. I had never used a FileReader before but I found it on your website and I learned quite a bit more about digging into forms with developer tools. So thanks for your help. – Cooper Apr 22 '20 at 19:57
  • @Cooper Thank you for your comment. The byte array is different between Javascript and Google Apps Script. By this, in this case, `Int8Array` is used instead of `unit8Array`. And when the byte array is sent to GAS side, it is required to convert it to an array. These are the points. I'm glad my sample script was useful for your situation. I think that your answer is also useful for users. So I would like to upvote. – Tanaike Apr 22 '20 at 23:39
  • 1
    My answer was a little bit different but that led me to learn a little more about digging into the structure of the form and when I finally got to using terms like this `form.elements.Destination.value` I finally empowered myself to build the object with the other values that I need like this `{FileName:form.elements.FileName.value,Destination:form.elements.Destination.value,mimeType:file.type,bytes:[...new Int8Array(e.target.result)]};` and it wasn't long after that that I was back to upload and insert images into Photo Library. I've never used a FileReader that was good to learn about. – Cooper Apr 22 '20 at 23:55
2

Upload File and Insert into Photo Library

You will need to insure that this is a GCP and have the Drive API and Photo Library API enabled.

I'm using the following scopes:

https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/script.container.ui https://www.googleapis.com/auth/script.external_request https://www.googleapis.com/auth/script.scriptapp https://www.googleapis.com/auth/spreadsheets

The next thing is that you cannot upload to albums created by a User. You need to create you own albums from a script.

Here's what I used:

function createAlbumByScript() {
  var requestBody={"album":{"title":"Uploads1"}};
  var requestHeader={Authorization: "Bearer " + ScriptApp.getOAuthToken()};
  var options = {
    "muteHttpExceptions": true,
    "method" : "post",
    "headers": requestHeader,
    "contentType": "application/json",
    "payload" : JSON.stringify(requestBody)
  };
  var response=UrlFetchApp.fetch("https://photoslibrary.googleapis.com/v1/albums",options);
  Logger.log(response);
}

I built an upload dialog as follows:

images.html:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <?!= include('resources') ?>
    <?!= include('css') ?>
  </head>
  <body>
    <?!= include('form') ?>
    <?!= include('script') ?>
  </body>
</html>

resources.html:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

css.html:

<style>
body {background-color:#ffffff;}
input[type="button"],input[type="text"]{margin:0 0 2px 0;}
#msg{display:none;}
</style>

script.html:

<script>    
  $(function(){  
    google.script.run
    .withSuccessHandler(function(vA) {
    $('#sel1').css('background-color','#ffffff');
      updateSelect(vA);
    })
    .getSelectOptions();
  });

  function updateSelect(vA,id){
    var id=id || 'sel1';
    var select = document.getElementById(id);
    select.options.length = 0; 
    for(var i=0;i<vA.length;i++)
    {
      select.options[i] = new Option(vA[i][0],vA[i][1]);
    }
  }    

  function displayAlbum() {
    var albumId=$('#sel1').val();
    var aObj={id:albumId};
    google.script.run
    .withSuccessHandler(function(iObj){
      $('#album').html(iObj.html);
      $('#msg').html(iObj.message);
    })
    .getAlbumDisplay(iObj);
  }

  function uploadFile(form) {
    console.log('form.elements.Destination.value= %s',form.elements.Destination.value);
    console.log('format.elements.FileName.value= %s',form.elements.FileName.value);
    $('#msg').css('display','block');
    $('#msg').html('UpLoading...Please Wait');
    const file=form.File.files[0];
    const fr=new FileReader();//all this FileReader code originally came from Tanaike
    fr.onload=function(e) {
      const obj={FileName:form.elements.FileName.value,Destination:form.elements.Destination.value,mimeType:file.type,bytes:[...new Int8Array(e.target.result)]};//this allowed me to put the rest of the Form.elements back into the object before sending it to google scripts
      google.script.run
      .withSuccessHandler(function(msg){
        $('#msg').css('display','block');
        $('#msg').html(msg);
      })
      .uploadFile(obj);
    }
    fr.readAsArrayBuffer(file);
  }

form.html:

<form>
  <br /><select id="sel1" name="Destination"></select> Upload Destination
  <br /><input type="text" name='FileName' /> File Name
  <br /><input type="file" name='File'; />
  <br /><input type="button" value="Upload" onClick="uploadFile(this.parentNode);" />
  <br /><input type="button" value="Close" onclick="google.script.host.close();" />
  <div id="msg"></div>
</form>

code.gs:

function listFiles() {
  var files = Drive.Files.list({
    fields: 'nextPageToken, items(id, title)',
    maxResults: 10
  }).items;
  for (var i = 0; i < files.length; i++) {
    var file = files[i];
    Logger.log('\n%s-Title: %s Id: %s',i+1,file.title,file.id);
  }
}

function uploadFile(obj) {
  SpreadsheetApp.getActive().toast('Here');
  var folder=DriveApp.getFolderById('1VAh2z-LD6nHPuHzgc7JlpYnPbOP_ch33');
  if(!obj.hasOwnProperty('FileName'))obj['FileName']="NoFileName";
  var blob = Utilities.newBlob(obj.bytes, obj.mimeType, obj.FileName);
  var rObj={};
  var ts=Utilities.formatDate(new Date(), Session.getScriptTimeZone(),"yyMMddHHmmss");
  var file=folder.createFile(blob).setName(obj.FileName + '_' + ts);
  rObj['file']=file;
  rObj['filename']=file.getName();
  rObj['filetype']=file.getMimeType();
  rObj['id']=file.getId();
  if(obj.Destination!=0){
    var uObj={albumid:obj.Destination,fileid:rObj.id,filename:rObj.filename};
    //Logger.log(JSON.stringify(uObj));
    addFileToPhotoLibrary(uObj);
    var msg=Utilities.formatString('<br/>File: %s<br />Type: %s<br />Folder: %s<br />File Added to Photo Library',rObj.filename,rObj.filetype,folder.getName());
    return msg;
  }else{
    var msg=Utilities.formatString('<br/>File: %s<br />Type: %s<br />Folder: %s',rObj.filename,rObj.filetype,folder.getName());
    return msg;
  }
}

function getUploadToken_(imagefileId) {
  var file=DriveApp.getFileById(imagefileId);
  var headers = {
    "Authorization": "Bearer " + ScriptApp.getOAuthToken(),
    "X-Goog-Upload-File-Name": file.getName(),
    "X-Goog-Upload-Protocol": "raw"
  };
  var options = {
    method: "post",
    headers: headers,
    contentType: "application/octet-stream",
    payload: file.getBlob()
  };
  var res = UrlFetchApp.fetch("https://photoslibrary.googleapis.com/v1/uploads", options);
  return res.getContentText()
}


function addFileToPhotoLibrary(uObj) {
  Logger.log(JSON.stringify(uObj));
  var imagefileId=uObj.fileid;  // Please set the file ID of the image file.
  var albumId=uObj.albumid;  // Please set the album ID.
  var uploadToken=getUploadToken_(imagefileId);

  var requestHeader = {Authorization: "Bearer " + ScriptApp.getOAuthToken()};
  var requestBody = {
    "albumId": albumId,
    "newMediaItems": [{
      "description": "Description",
      "simpleMediaItem": {
      "fileName": uObj.filename,
      "uploadToken": uploadToken
    }}]
  };
  var options = {
    "muteHttpExceptions": true,
    "method" : "post",
    "headers": requestHeader,
    "contentType": "application/json",
    "payload" : JSON.stringify(requestBody)
  };
  var response = UrlFetchApp.fetch("https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate", options);
  Logger.log(response);
}

function createAlbumByScript() {
  var requestBody={"album":{"title":"Uploads1"}};
  var requestHeader={Authorization: "Bearer " + ScriptApp.getOAuthToken()};
  var options = {
    "muteHttpExceptions": true,
    "method" : "post",
    "headers": requestHeader,
    "contentType": "application/json",
    "payload" : JSON.stringify(requestBody)
  };
  var response=UrlFetchApp.fetch("https://photoslibrary.googleapis.com/v1/albums",options);
  Logger.log(response);
}

function  launchUploadDialog() {
  loadOptionsPage();
  var ui=HtmlService.createTemplateFromFile('images').evaluate();
  SpreadsheetApp.getUi().showModelessDialog(ui, "Image Upload Dialog")
}

function loadOptionsPage() {
  var ss=SpreadsheetApp.getActive();
  var sh=ss.getSheetByName('Options');
  sh.clearContents();
  var dA=[['Destination','Id'],['Uploads Folder',0]];
  var aA=listAlbums();
  aA.forEach(function(a,i){
    dA.push([a.title,a.id]);
  });
  sh.getRange(1,1,dA.length,dA[0].length).setValues(dA);
}

function getSelectOptions() {
  var ss=SpreadsheetApp.getActive();
  var sh=ss.getSheetByName('Options');
  var rg=sh.getRange(2,1,sh.getLastRow()-1,2);
  var vA=rg.getValues();
  return vA;
}
Cooper
  • 59,616
  • 6
  • 23
  • 54