I built a new tab page to look at my old pictures

February 06, 2023 · 8 mins read

Photography is my main hobby. I have been taking pictures for 17 years now, and while I do have a selection of my best work, 17 years worth of pictures is a lot more than that.

This means that there are thousands upon thousands of image files gathering digital dust on a spinning cobalt disk inside my NAS. Each one of those pictures was shot at a moment where I thought it was important, passed the culling process then carefully processed in Lightroom. Realizing that all these pictures will probably never be looked at again made me sad, so I decided to do something about it.

The idea

Over the weekend, I had a thought: what if I could make my browser’s new tab page display a random picture from my stash? The image would change at regular intervals, keeping the novelty factor high and blowing some digital dust off the hard drive.

This had me think back to the old Webshots desktop setup I had growing up, that would change my desktop wallpaper every hour. It also incidentally turned me into a desktop image hoarder, but that’s a story for another time.

I rarely look at my desktop wallpaper these days, so I kept the idea but switched the canvas to be the browser new tab page, which gets opened dozens of times per day.

Prototype

The first step is to build a database of all the source pictures. A text file is enough:

$ find /Volumes/home/Pictures/Processed -type f -name '*.jpg' > all_pictures.txt
$ wc -l all_pictures.txt
    32588 all_pictures.txt

There are 32,588 pictures to chose from. To get one from the set, I can use shuf:

$ shuf -n 1 all_pictures.txt
/Volumes/home/Pictures/Processed/2013/décembre/NNC 2013/IMG_7683-3.jpg

All that’s left is to wrap this in a web server with some minimal styling, and voilà:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'sinatra'
  gem 'puma'
end

class App < Sinatra::Base
  get '/' do
    # Need to copy the image to the `public` folder because browsers
    # cannot load local files.
    `cp "$(shuf -n 1 all_pictures.txt)" public/chosen.jpg`
    
    "<img src='chosen.jpg' style='max-width: 90%; display: block; margin: auto;'/>"
  end

  run! if app_file == $0
end

I now had a page that displayed a random image, loaded on every refresh

Bells and whistles

Caching

Loading a different image on each reload is fun, but also very distracting and bandwidth-heavy. The solution was a simple in-memory cache to keep the same image loaded:

# cache.rb
class Cache
  def initialize
    @store = {}
  end

  def with_cache(key, &block)
    cache_hit = get(key)
    return cache_hit if cache_hit

    value = block.call
    set(key, value)
    value
  end

  private

  TTL_SECONDS = 5

  attr_reader :store

  def set(key, value)
    store[key] = { value: value, expires_at: Time.now.to_i + TTL_SECONDS }
  end

  def get(key)
    return if store[key].nil?
    return if Time.now.to_i >= store[key][:expires_at]

    store[key][:value]
  end
end

# server.rb
set :cache, Cache.new

get '/' do
  App.cache.with_cache(:image) do
    # ...
  end
end

Styling and metadata extraction

Using the exif gem, I could extract some EXIF metadata from the image file to provide more context about the image.

Add some extra styling and you have the final look of the page:

The final picture, with a full-width picture and metadata at the bottom

Setting the new tab page

This proved surprisingly harder that I expected, for a couple reasons:

  • Firefox doesn’t support for custom urls for new tabs anymore, and recommends using browser extensions instead.
  • When I tried some extensions, the behavior was odd: The new page opened, but it also focused the url and you’d have to manually clear the address bar before you can enter a new address. I lasted exactly 4 minutes before uninstalling them. There’s even a long-standing bug report about this.

After trying multiple extensions, I found one that managed to bypass this by embedding the desired page in an iframe.

This came with its own challenges, as browsers don’t allow pages to be embedded in arbitrary iframes anymore (nor should they). The solution is to set the CSP frame-ancestors directive on the server response to allow this:

response.headers['Content-Security-Policy'] = 'frame-ancestors *;'

This was the last piece of the puzzle, and I can now enjoy a new picture from my archive every hour.

Epilogue

A few hours in

In the beginning, the time between each new picture was set to 5 minutes. After a couple hours, I found myself compulsively reaching for my laptop to open a new tab and check the new picture. I accidentally hooked myself on a picture-induced dopamine rush!

When I realized that, I switched the timer from 5 minutes to 1 hour and that feeling disappeared.

A few days in

After a couple days using it, I have:

  • Had many trips down memory lane.
  • Sent some of the pictures to people I was with in the pictures, usually traveling.
  • Looked up way too many people online to see what they were up to now.

I do have some ideas for future improvements, if I ever get to them:

  • Also set the picture as a desktop background?
  • Dockerizing the server so it can be run directly on the NAS instead of locally
  • Saving the metadata along with the images to enable functionalities like “On this day”
  • Adding a way to skip pictures that I don’t want to look at (I just restart the server when it happens)