Tuesday, January 28, 2020

Google Photos API Giveth, and Then Promptly Taketh Away

Last week I was psyched: the Google Photos API had added the functionality I needed to allow for command line album management. No longer would I clumsily create and add photos to albums in a web interface. No, I'd only have to kick off a single shell script that would do all the work for me.

Using the API, I'd built out the support for querying album and images. And this week I added support for creating albums. It was all going so very well:

# Make a new album
$ gphoto_tools -a album-new -n "gphoto_tool test" 
AHnaBgvE-lqCduDAiPiMmDEIKyeEwcRALqCoK8bn-oOdZFMju06qrT8VbopDIGs-9Wh0XAmZs8ei

# Add heaps of photos to it
$ gphoto_tools -a media-list | sed 's/|.*//' | \
  gphoto_tools -a album-add -i 'AHnaBgvE-lqCduDAiPiMmDEIKyeEwcRALqCoK8bn-oOdZFMju06qrT8VbopDIGs-9Wh0XAmZs8ei'

Except, the above code doesn't work. It gives me a 400 error:

{
  "error": {
    "code": 400,
    "message": "Request contains an invalid media item id.",
    "status": "INVALID_ARGUMENT"
  }
}

I checked and re-checked the album and media IDs. Everything seemed right. I then tried using the API Explorer in the docs and got the same error. Uh, oh.

A bit of searching explained the problem. The Manage Albums Developer Guide includes this fine print (emphasis added):

Note that you can only add media items that have been uploaded by your application to albums that your application has created. Media items must also be in the user's library. For albums that are shared, they must either be owned by the user or the user must be a collaborator who has already joined the album.

In other words: adding previously uploaded photos to an album created by the API is a no-no. Why, Google?! Why?!

I suppose this is some sort of security protection, but I don't get. I'm all for sandboxing apps to protect users, but this seems to go too far. Effectively you can write an app that uploads and organizes photos, but you can't write an app who's primary job is just to organize. This seems short sighted, especially because album creation and organization is totally separate from the act of uploading photos.

And really Google, you can't include this critical limitation in the API reference pages?

Below is the latest version of the gphoto_tools script I've been working on. It includes the operations album-new and album-add, though obviously this doesn't work.

Ugh. You're killing me Google Photos API, you're kill me.

#!/bin/bash

##
## command line tool for working with the google photos API.
## https://developers.google.com/photos/library/guides/overview
##

API_BASE=https://photoslibrary.googleapis.com/v1
AUTH_TOKEN=`gphoto_auth token`
ID_COL=.id

function usage {
  echo -n "Usage: `basename $0` "
  echo -n "-a {album-list|album-get|media-list|media-get|album-new|album-add|album-rm} [-q json-query] [-i id] [-u] [-n name]"
  echo ""
  exit
}

while getopts ":a:q:i:n:vu" opt; do
  case $opt in
    a) ACTION=$OPTARG ;;
    q) QUERY=$OPTARG  ;;
    i) ID=$OPTARG     ;;
    n) NAME=$OPTARG   ;;
    v) VERBOSE=yes    ;;
    u) ID_COL=.productUrl ;;
    \?) usage         ;;
  esac
done

function invoke {
  buffer=/tmp/gphoto.buffer.$$
  curl -s -H "Authorization: Bearer $AUTH_TOKEN" "$@" > $buffer
  cat $buffer
}

function filter {
  if [ -z "$VERBOSE" ] ; then
    jq "$@"
  else
    cat
  fi
}

case $ACTION in
  album-list)
    invoke -G $API_BASE/albums | filter -r ".albums[] | $ID_COL + \"|\" + .title"
    ;;
  album-get)
    if [ -z "$ID" ] ; then
      echo "Missing -i album-id"
      usage
    else
      invoke -G $API_BASE/albums/$ID | filter -r '.productUrl'
    fi
    ;;
  media-list)
    if [ -z "$QUERY" ] ; then
      invoke $API_BASE/mediaItems | filter -r ".mediaItems[] | $ID_COL + \"|\" + .filename"
    else
      invoke -X POST $API_BASE/mediaItems:search -H "Content-Type: application/json" -d "$QUERY" | filter -r ".mediaItems[] | $ID_COL + \"|\" + .filename"
    fi
    ;;
  album-new)
    if [ -z "$NAME" ] ; then
      echo "Missing -n album-name"
      usage
    else
      invoke -X POST $API_BASE/albums -H "Content-Type: application/json" -d "{\"album\" : { \"title\" : \"$NAME\" } }" | filter -r "$ID_COL"
    fi
    ;;
  album-add)
    if [ -z "$ID" ] ; then
      echo "Missing -i album-id"
      usage
    else
      echo '{ "mediaItemIds" : [' > /tmp/body.$$
      sep=''
      while read line ; do
        echo $line | sed -e "s/^/${sep}\"/" -e 's/$/"/' >> /tmp/body.$$
        sep=','
      done
      echo '] }' >> /tmp/body.$$
      invoke -X POST $API_BASE/albums/$ID:batchAddMediaItems -H "Content-Type: application/json" -d @/tmp/body.$$
      rm /tmp/body.$$
    fi
    ;;
  media-get)
    if [ -z "$ID" ] ; then
      echo "Missing -i <id> value"
      usage
    else
      invoke $API_BASE/mediaItems/$ID | filter -r '.productUrl'
    fi
    ;;
  *)
    usage
    ;;
esac

No comments:

Post a Comment