hi, it's mike ʕ•ᴥ•ʔノ

Making a Picture of the Week feature on Hugo (Updated)

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:

1params:
2  potw_img_url: https://photos.smugmug.com/photos/i-TkDZjHx/0/ce3d02d6/XL/i-TkDZjHx-XL.jpg
3  potw_caption: Manzanita Beach at Dawn
4  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.
5  potw_gallery_link:  https://pix.puddingtime.org/Uploads/n-47GBfb/i-TkDZjHx

… then pull use a partial:

 1<div class='front-image'>
 2<figure>
 3  <a href="{{ .Site.Params.potw_gallery_link  }}" alt="{{ .Site.Params.potw_alt }}">
 4    <img src="{{ .Site.Params.potw_img_url }}" />
 5  </a>
 6<figcaption>
 7Picture of the Week: {{ .Site.Params.potw_caption }}
 8</figcaption>
 9</figure>
10</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:

1<figure>
2  <a href="{{ $.Page.Params.potw_gallery_link  }}" alt="{{ $.Page.Params.potw_alt }}">
3    <img src="{{ $.Page.Params.potw_img_url }}" />
4  </a>
5<figcaption>
6{{ $.Page.Params.title }}
7</figcaption>
8</figure>

As you can see, the frontmatter needs to have a few extra bits in it:

 1---
 2title:  'Picture of the Week: Profit from the Panic'
 3potw_img_url: https://photos.smugmug.com/photos/i-87Bm3V2/0/ecba06f4/XL/i-87Bm3V2-XL.jpg
 4potw_alt: Wheatpaste of a tv mounted on a human body giving a thumbs up. The TV reads "Profit from the Panic"
 5potw_gallery_link:  https://pix.puddingtime.org/Picture-of-the-Week/n-D5HJ4W/i-87Bm3V2
 6
 7date: 2023-02-01T20:10:15-0800
 8tags: ['potw','photography']
 9draft: false
10---

imgup’s PotW page gives me the YAML frontmatter:

A screenshot of a browser showing a page that has YAML snippets next to thumbnails of photos.

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:

 1{{ range first 1 .Site.Taxonomies.tags.potw }}
 2    
 3<div class='front-image'>
 4<h3>Picture of the Week</h3>
 5
 6<figure>
 7  <a href="{{ .Page.Params.potw_gallery_link  }}" alt="{{ .Page.Params.potw_alt }}">
 8    <img src="{{ .Page.Params.potw_img_url }}" />
 9  </a>
10<figcaption>
11{{ .Page.Params.title }}
12</figcaption>
13</figure>
14</div>
15{{ 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:

1unless json == true
2  haml :potw
3else 
4  content_type  :json
5  @recents.first.to_json
6end

A simple script hits the endpoint, grabs the JSON, and writes it out to a file:

 1##!/usr/bin/env ruby
 2
 3require 'net/http'
 4require 'uri'
 5require 'json'
 6require 'date'
 7require 'slugify'
 8require 'yaml'
 9require 'optparse'
10
11# set default tags for each entry. The "tags" option allows you to add more
12tags = ['photography', 'potw']
13
14options = {}
15
16OptionParser.new do |parser|
17  parser.on("-t", "--test", "Changes the endpoint to localhost:4567") do |o|
18    options[:test] = true
19  end
20
21  parser.on("-o", "--overwrite", "Danger: Overwrite the existing potw if there's a conflict.") do |o|
22    options[:overwrite] = true
23  end
24
25  parser.on("-T", "--tags TAGS", "Comma-delimited tags for the post, e.g. 'banana,apple,pear'") do |o|
26    options[:tags] = o.split(',')
27    options[:tags].each do |t|
28      tags << t
29    end
30  end
31
32  parser.on("-h", "--help", "Get help.") do |o|
33    puts parser
34    exit(0)
35  end
36
37end.parse!
38
39if options[:test] == true
40  endpoint = 'http://localhost:4567/potw?json=1'
41else
42  endpoint = 'https://imgup.puddingtime.org/potw?json=1'
43end
44
45site_posts_dir = "~/src/simple/content/posts/"
46
47date = Date.today.strftime("%Y-%m-%d")
48long_date = Time.now.strftime("%Y-%m-%dT%H:%M:%S%z")
49title = date + '-potw'
50
51slug = title.slugify
52filename =  slug + '.md'
53file_path = File.expand_path(site_posts_dir + filename)
54
55if File.exists?(file_path) && options[:overwrite] != true
56  abort("*** Error: #{file_path} already exists. Exiting.")
57end
58
59
60uri = URI.parse(endpoint)
61request = Net::HTTP::Get.new(uri)
62request.basic_auth(ENV['IMGUP_USER'], ENV['IMGUP_PASS'])
63
64req_options = {
65  use_ssl: uri.scheme == "https",
66}
67
68response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
69  http.request(request)
70end
71
72json = JSON.parse(response.body)
73yaml = YAML.dump(json)
74
75potw_post = <<-POTW
76#{yaml}
77
78date: #{long_date}
79tags: #{tags}
80categories: ['photography']
81draft: true
82---
83
84# escaped  to keep hugo from parsing this as a shortcode: 
85# {\{< potw >}}
86
87POTW
88
89File.write(file_path, potw_post)
90
91`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.

#Hugo #Photography