12 December 2016

Playing with Ruby and Flickr

ruby

One of the best things about being a developer is the ability to play with computers, getting them to do cool things that don’t really serve a purpose. Often, the only limit is your imagination and the free time you have. In this vain I looked at getting ruby to download the top photo(s) from flickr and setting them automatically as my MacOS wallpaper. I had fun, so I thought I’d write down my experience.

Downloading the Photos

Flickr have a pretty decent REST API you can use to build client apps and getting hold of an api key is pretty simple and free, but you do need a Yahoo! account. Some bright spark in marketing came up with that, I’m sure ;P.

Ruby is known for its community and the wealth of gems they create. Flickr have an open API and have had it for a while so you can be fairly sure there will be a client gem for it. Lo and behold: flickraw.

The flickraw gem provides a pretty standard object oriented api client object; with methods for each of the flickr API endpoints and a constructor that takes your api key and shared secret.

Now onto what the API actually provides. What I want is a way to get a ‘good’ picture and I want it to change often - I want a ‘Photo of the Day’. Luckily there is a part of the API directly for that, it is called interestingness, what a great name. This is used internally to populate flickr’s explore page, which is actually how I found it.

There is exactly one endpoint under the namespace of interestingness, that is flickr.interestingness.getList. The default behaviour of the endpoint is return to the top 100 ‘interesting’ photos today. This sounds like exactly what I wanted when I started, but with a few more photos than I was expecting, easily changed though.

The code I used took the number of photos to get as a command line argument to the script. I used ARGV but I could easily have used optparse if I wasn’t being so lazy. Hey, I’m just playing around here.

flickr = FlickRaw::Flickr.new("YOUR API KEY", "YOUR SHARED SECRET")

photos = flickr.interestingness.getList(per_page: ARGV[0])

photos.each do |photo|
  photo_url = flickr.photos.getSizes(photo_id: photo.id)[-1].source
  photo_format = photo_url.split('.').last

  photo_file = "~/Photos/Wallpapers/flickr/#{photo.title}#{photo_format}"

  File.open(photo_file, "wb") do |f|
    open(photo_url, "rb") do |ph|
      f.write(ph.read)
    end
  end

  set_photo_as_desktop_background(photo_file)
end

The response for the most interesting photos returns not much more than the photo’s ID and name. Annoying, but understandable. I wanted to download the photo and use it as a wallpaper, so I needed to know the URI for the largest possible copy. There’s an endpoint for that! This is all very familiar and REST-ful, well done flickr and flickraw.

Perhaps the only disappointing this so far is the fact the neither the format nor the file extension for the photo seem to be available in the API. So I’m manually extracting it from the URI, not particularly pretty, but not really that brittle so I’m not too bothered.

Coming from a Java background, I’m always worried when I have to deal with real things in programming. By real things here, I mean dates, times and of course, Files. Eww. Ward Cunningham said that clean code “makes it look like the language was made for the problem” meaning that a programmer should make the code look elegant, even when working with files. So when we work safely with resources in Java, and we make a mess, it is up to us to make that mess be elegant. So I have learnt to hate opening files and writing bytes, but ruby always manages to make it look easy, and that is why I love it.

Actually Setting the Background

So now I’m able to download photos from flickr and save them locally, only thing left is to set my background. I’m running on MacOS, which is largely unix based so this sort of thing should be easy. Unfortunately the window manager and desktop organiser is exactly where MacOS diverges from the unix systems that gave rise to linux.

In fact, MacOS ships with Automator a tool to help with repetitive tasks that would otherwise require the use of the UI. It even includes its own scripting language, AppleScript that you can use to build your own actions. This sounds like what I want and has the ability to change the background for the current space, but for some reason it seems that it cannot work on multiple spaces.

You can directly set the preference by editing the plist, which is pretty easy when using the defaults command.

defaults write com.apple.desktop "Background" "~/Pictures/flickr/image.jpg"

But this requires you to restart the desktop manager (called Dock) for the change to take effect. This approach sucks for a number of reasons such as minimising all my open applications. It also seemed at bit flaky for reasons I could not understand.

Even when this was working, it was only setting a single photo as the current background. One of the features I like about MacOS is how easy it is to get rotating backgrounds. This works in the UI by choosing a folder, so maybe I could skip all the barely supported MacOS automation and just fill a pre-chosen folder with my images and let the system rotate them.

Turns out this works out of the box, it even includes newly added pictures in the rotation without the need for restarting anything, so my script can be run as a cron job without effecting me. I wondering why I spent an hour looking at AppleScript documentation and forums now.

That’s All Folks!

So I had some fun today. Ruby is a wonderful language to use for all sorts of things, but especially excels in making little scripts for your own pleasure. You can find the source in this gist. I’ll probably clean it up from what I wrote earlier.