Updated: See the last section
I wanted to take advantage of the flexibility I gave myself with Hugo to have a “Picture of the Week” (PotW) feature on my new site. It took a few iterations to get it to where I liked it, and there are some things about Hugo I learned along the way, but it’s done enough for now.
The basic idea is that I want to take advantage of the streamlined upload and metadata workflow I’ve set up between Lightroom and imgup to share photos without a lot of repeating myself when it comes to writing titles, alt text, etc.
First try ๐
My first iteration was to keep the data for a featured picture in the site config, like this:
params:
potw_img_url: https://photos.smugmug.com/photos/i-TkDZjHx/0/ce3d02d6/XL/i-TkDZjHx-XL.jpg
potw_caption: Manzanita Beach at Dawn
potw_alt: A rocky beach at dawn with hills and a mountain shrouded in mist. A person in a red jacket looks over the ocean.
potw_gallery_link: https://pix.puddingtime.org/Uploads/n-47GBfb/i-TkDZjHx
… then pull use a partial:
<div class='front-image'>
<figure>
<a href="{{ .Site.Params.potw_gallery_link }}" alt="{{ .Site.Params.potw_alt }}">
<img src="{{ .Site.Params.potw_img_url }}" />
</a>
<figcaption>
Picture of the Week: {{ .Site.Params.potw_caption }}
</figcaption>
</figure>
</div>
That worked fine for my purposes, but I didn’t like all the clicking to get SmugMug photo info into the config, and I hated the idea of touching my config to do a simple task.
It also had no memory of previous images and I preferred the idea of building a list of these items over time to make a specific PotW page, or to just have them in the record.
What I’ve settled on now is a little less clicky, and I’ve got an idea for how to make it even less clicky.
Second try ๐
I started by setting up a specific PotW album in SmugMug. Anyone with the URL can see it, but it’s not exposed right now. I upload photos to there.
Next, I made a shortcode that can talk to metadata stored in a page:
<figure>
<a href="{{ $.Page.Params.potw_gallery_link }}" alt="{{ $.Page.Params.potw_alt }}">
<img src="{{ $.Page.Params.potw_img_url }}" />
</a>
<figcaption>
{{ $.Page.Params.title }}
</figcaption>
</figure>
As you can see, the frontmatter needs to have a few extra bits in it:
---
title: 'Picture of the Week: Profit from the Panic'
potw_img_url: https://photos.smugmug.com/photos/i-87Bm3V2/0/ecba06f4/XL/i-87Bm3V2-XL.jpg
potw_alt: Wheatpaste of a tv mounted on a human body giving a thumbs up. The TV reads "Profit from the Panic"
potw_gallery_link: https://pix.puddingtime.org/Picture-of-the-Week/n-D5HJ4W/i-87Bm3V2
date: 2023-02-01T20:10:15-0800
tags: ['potw','photography']
draft: false
---
imgup’s PotW page gives me the YAML frontmatter:
I can copy and paste it into a PotW post along with the shortcode and it’s ready to go.
Pulling it into the front page is just a partial that pulls in the most recent item tagged potw
:
{{ range first 1 .Site.Taxonomies.tags.potw }}
<div class='front-image'>
<h3>Picture of the Week</h3>
<figure>
<a href="{{ .Page.Params.potw_gallery_link }}" alt="{{ .Page.Params.potw_alt }}">
<img src="{{ .Page.Params.potw_img_url }}" />
</a>
<figcaption>
{{ .Page.Params.title }}
</figcaption>
</figure>
</div>
{{ end }}
What’s next ๐
It was quick and easy to just copy a route in imgup to make the PotW page, but it’s still manual and clicky to make a PotW post. So my next step will be to hang an endpoint off imgup that automates the process of getting the most recent PotW gallery image and its metadata so I don’t have to write the posts at all.
Update ๐
Sinatra made it easy to add a tiny bit of logic to the existing PotW route to make it send back JSON:
unless json == true
haml :potw
else
content_type :json
@recents.first.to_json
end
A simple script hits the endpoint, grabs the JSON, and writes it out to a file:
##!/usr/bin/env ruby
require 'net/http'
require 'uri'
require 'json'
require 'date'
require 'slugify'
require 'yaml'
require 'optparse'
# set default tags for each entry. The "tags" option allows you to add more
tags = ['photography', 'potw']
options = {}
OptionParser.new do |parser|
parser.on("-t", "--test", "Changes the endpoint to localhost:4567") do |o|
options[:test] = true
end
parser.on("-o", "--overwrite", "Danger: Overwrite the existing potw if there's a conflict.") do |o|
options[:overwrite] = true
end
parser.on("-T", "--tags TAGS", "Comma-delimited tags for the post, e.g. 'banana,apple,pear'") do |o|
options[:tags] = o.split(',')
options[:tags].each do |t|
tags << t
end
end
parser.on("-h", "--help", "Get help.") do |o|
puts parser
exit(0)
end
end.parse!
if options[:test] == true
endpoint = 'http://localhost:4567/potw?json=1'
else
endpoint = 'https://imgup.puddingtime.org/potw?json=1'
end
site_posts_dir = "~/src/simple/content/posts/"
date = Date.today.strftime("%Y-%m-%d")
long_date = Time.now.strftime("%Y-%m-%dT%H:%M:%S%z")
title = date + '-potw'
slug = title.slugify
filename = slug + '.md'
file_path = File.expand_path(site_posts_dir + filename)
if File.exists?(file_path) && options[:overwrite] != true
abort("*** Error: #{file_path} already exists. Exiting.")
end
uri = URI.parse(endpoint)
request = Net::HTTP::Get.new(uri)
request.basic_auth(ENV['IMGUP_USER'], ENV['IMGUP_PASS'])
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
json = JSON.parse(response.body)
yaml = YAML.dump(json)
potw_post = <<-POTW
#{yaml}
date: #{long_date}
tags: #{tags}
categories: ['photography']
draft: true
---
# escaped to keep hugo from parsing this as a shortcode:
# {\{< potw >}}
POTW
File.write(file_path, potw_post)
`open #{file_path}`
Much more turnkey than it was, and with a little more logic I might be able to build it into a pre-build script of some kind to completely automate the whole thing.