Some Basics And Some Real Work Using Google App Script

Rick Lowrey
7 min readJun 18, 2020

A short flight into Cloud Computing

Del Mar Bluff, March 2017

I recently had a request from my lovely ex-engineer-now-artist girlfriend that she wanted to rename a bunch of image files on her Google Drive to include the dimensions of the image in the filename. After some discussion, we decided we didn’t really need to keep the original files intact. Her Google Drive was being fed by a web site where an artist could upload images, and those images were shoved into a sub-folder named as a date/time stamp. Later, she would manually check the contents of new directories to make sure naming standards were followed, then change the folder name to the Artist’s name. Seems pretty simple. We weren’t converting the images, or resizing, or anything.

I had just completed a Python course where one of the exercises was extracting EXIF (Exchangeable Image File Format) properties from a bunch of image files. I figured that getting simple pieces of metadata would be a snap. Couple that with my recent certifications in Google Cloud Platform and I was pretty confident I could knock something out for her quickly.

One thing to note is that Google App Script is more like JavaScript than Python. Not a worry for me, since I’ve been dabbling in JS for a few years. Google App Script can be embedded in several of the Google Docs document types, or created as stand-alone functions. I chose to write this stand-alone, since this particular use case was something that manipulated files rather than content. So, the beginning was going to Google Drive, clicking the colorful plus sign, clicking on “More…” and creating a new Google Apps Script. I suggest you give your project a suitable name by clicking on “Untitled project” and typing something…well…suitable. The default filename is “Code.gs”, but you might choose to change that too. I didn’t. Also, the pre-populated function name is “myFunction()”. You will probably want to change that as well. Based on my research and seeing what others had done, I chose “start()” as my function name since it’s a batch process that does lots of iterating.

The first step in manipulating a bunch of files is (obviously) to be able to get a list of the files. Google Drive App Script allows you to do this through a class object called DriveApp. It has several useful methods, but the specific one I wanted was DriveApp.getFoldersByName. It takes a string parameter that equates to the name of the folder. It would provide the starting point for our job.

var startFolderName = “Artist Image Submission Form”;
var startFolder = DriveApp.getFoldersByName(startFolderName).next();

Please notice the method name is getFolderSSSSbyName. The object returned by this method is something called a FolderIterator. It is defined as a LIST of folder objects. Even if there is only one folder you’re interested in, it will return a list containing one object. So, in order to get an actual folder, you have to fetch the first object in the list. Depending on your use case, it might be preferable to use some sort of loop structure like “for” or “while”. For my purposes, since I knew I only had one folder to work with I just chained the call with “.next()” to get the necessary object.

Okay, I have my folder where I want to iterate through all the subfolders. Now, I need to actually get the subfolders. Conveniently, there’s a method for that called getFolders().

var folders = startFolder.getFolders();

Once I have my list of folders to iterate through I have to actually get a list of files. However, I didn’t want every file. I only wanted image files. So, for each folder in my list I used the “getFilesByType” function to return an iterable list of files.

var files = folder.getFilesByType(MimeType.JPEG);

After quite a bit of searching, I found the specific list of available MIME types. There’s even a nice little example script that gives you a little idea of how to get some information about images. The bad news is, the example script didn’t show how to get at the information I was looking for. I specifically needed image dimensions (height and width) to append to her image file names.

But wait a second, here. This function will only get files of ONE type of image! What if some artist decides he doesn’t like JPEG and wants to upload a PNG file? Or a BMP? Fortunately I was able to find an example here that helped me. I was able to adapt his example like so:

var mimeTypes = [MimeType.JPEG, MimeType.PNG]

for (var i = 0; i < mimeTypes.length; i++) {
var files = folder.getFilesByType(mimeTypes[i]);
.
.
.
}

Please notice that the members of the list are actually values from the MimeType Enum. Do not mistake these for string values to pass in (I did, and it took me a few minutes to figure out why all my files were being ignored).

Next I needed to find out how to get at the details available from the metadata properties. I discovered during my Python class that not every image will have an EXIF block to look at. How to deal with that is beyond the scope of my article today, but I did include a ternary test in the getMetaData function to at least not return null in case the EXIF block didn’t exist. For our purposes, we assume EVERY image will have some metadata, specifically the pixel dimensions I needed for my task. I did a bunch of searching and finally found what I was looking for. That post had a link to this page that gave me everything I needed to tease out the bits I required.


.
.
.
while(files.hasNext()) {
var file = files.next();
var metaData = getMetaData(file.getId());
var width = (metaData == {}) ? 100: metaData.width;
var height = (metaData == {}) ? 100 : metaData.height;
.
.
.
}
}
function getMetaData( fileId ) {
var file = Drive.Files.get(fileId);
var metaData = file.imageMediaMetadata;
return metaData == null ? {} : metaData;
}

Next up, I needed to pick apart the original file name so I could cobble together the interesting pieces into a new file name. I used the tried-and-true JavaScript “slice” method to get what I needed using the indexOf method to get the index of the file separator. Once I had those parts, I was able to fashion the new name by simple concatenation.

var fullName = file.getName();
var newName = fullName.slice(0, fullName.indexOf(“.”));
var newExt = fullName.slice(fullName.indexOf(“.”) + 1);
var newFullName = newName + ‘-’ + width + ‘x’ + height + ‘.’ + newExt;

Could I have done it more elegantly? Yeah, probably. But I was getting close to the finish line so I let it pass for now.

We’re not quite finished. We still need to do the actual “renaming”. Mercifully, there is a function to do this: setName(). Once you have a well-formed string (or maybe a poorly formed string…I didn’t really test that part) do this:

file.setName(newFullName);

Hold everything… What if I had already processed this folder? I would wind up with file names like “SassyCat-400x200–400x200–400x200.jpg”. That wouldn’t do. So, at the end of processing a folder, I just create a little “processed.txt” file that tells the function we have no work to do here (“These aren’t the files you’re looking for…you can go about your business…move along…”).

while (folders.hasNext()) {
var folder = folders.next();
Logger.log(“ folder: %s”, folder);
if (folder.getFilesByName(“processed.txt”).hasNext() == false) {
.
.
.
}

Here’s my full script, in complete order:

Oops…one other little thing.

When you go to run this script, it’s going to complain that “Drive.Files….” is not a valid object. It is important that you enable the necessary API’s in your project.

From your script editor at script.google.com, look for the “Resources” menu item, and click on “Advanced Google services…”. Scroll down the list until you find “Drive API” and click on the slider to set it to “on” and click OK. That will give you the necessary services to allow you to run this script.

Second “one other little thing”. In my Logger.log function calls and my Utilities.formatDate calls, I use the explicit timezone for formatting. I didn’t do more research to see if I could make this dynamic (as in, the time zone of the user of the script). If you choose to do so, please post a comment. For the rest of us, here’s a handy reference site containing all the official timezone names.

Third “one other little thing”. Since this is a batch process, it’s probably reasonable to schedule it. You do this using Triggers. From your script editor at script.google.com, click on the Clock icon in the task bar (or on the “Edit” menu item, and “Current project’s triggers”), then click “Create a trigger”. You can select a host of options which I won’t go into. The most important thing is to select the function to run. I chose the “start” function, event source Time-driven, Day timer, 3am to 4am time of day (GMT-07:00). Your requirements may vary.

The happy end of my story is that the function runs as my girlfriend wants, once per day. It gives her exactly the result she is looking for, and I’m now a hero. Well, maybe “hero” is a bit of exaggeration. But she’s pleased that I’ve helped save her probably a dozen hours of tedious manual work.

I hope this article is helpful to someone out there. Happy clouding!

--

--