added cardy bee post
Deploy Website / build (push) Successful in 21s Details

This commit is contained in:
James Ravenscroft 2024-10-29 08:38:54 +00:00
parent de8335f4e2
commit ed96e053e7
1 changed files with 85 additions and 0 deletions

View File

@ -0,0 +1,85 @@
---
title: "Generating (Non-AI) Social Images with Python and Hugo"
date: 2024-10-29T08:37:07Z
draft: false
description: A short post explaining how I use a Python script to generate (non-ai) social preview images for my site
url: /2024/10/29/cardy-bee
type: posts
mp-syndicate-to:
- https://brid.gy/publish/mastodon
- https://brid.gy/publish/twitter
tags:
- colophon
- blogging
- pompidou
---
These days if you link to a blog post from a social media site like Mastodon or BlueSky the site will load your page and look for metadata element tagged `og:image` and render it inline as part of the link out to the article. If you don't supply one of these images, the social site will typically just show a grey rectangle or a "broken image" icon. Therefore, most of the time, when posting a new article I try to set a "feature image" to avoid this ugliness.
I know a lot of people are using AI-generated images as featured images and I did have a brief flirtation with this last year but I've gone right off that idea.
Instead, I was inspired to generate title cards for my posts. I read a few different posts on the topic but ended up largely following [Elio Struyf's approach](https://www.eliostruyf.com/generate-open-graph-preview-image-code-front-matter/). I reimplemented his typescript/nodejs tool in Python using the [html2image](https://pypi.org/project/html2image/) library. I am using a Python script to launch Chrome into headless mode and render static screenshots of the title card ( I am using a modified version of Elio's HTML template). Then I'm storing these files in my hugo static images directory and adding a metadata entry to each blog post to tell it where it's corresponding OG image lives.
## Cardy-🐝 image generation script
My script, `cardy-bee` is very bespoke to my hugo setup but it is available under the GPL license from [here](https://git.jamesravey.me/ravenscroftj/cardy-bee) The script runs every time a change gets made to my website repo (which would indicate that I've added a new article or edited/removed an existing one).
I don't version control the images since they can be reliably re-generated automatically. I do cache them by generating a hash of the post title and publication date and using that for the image filename. The script quickly checks all existing posts to see if they have a corresponding title card set in the hugo front matter and whether that file exists. It will then generate the image as necessary.
If there is a change to the title or date then the hash will change and the generated filename will be different. This should trigger the script to automatically generate a new title card and if the old file still exists, it will rename it out of the way to `old_name.png.cleanup`. Then I can just run `rm *.cleanup` periodically to get rid of old thumbnails. I could have fully automated this step but I like the idea of being able to go in and check/restore old thumbnails.
Each image is about 42kb in size so they're not horrendous to keep around (and they should load pretty quickly over slow-ish connections too).
## Adding the Images to Hugo
By default cardy-🐝 will store the image name in the hugo post frontmatter under the `preview` attribute. After that, I've got a rather ugly snippet in my Hugo template which checks for different possible images in the frontmatter. Firstly we check for a `preview` attribute and if that's not there we check for other possible image metadata (my micropub tool generates `photo` entries when I upload pictures I've taken).
```
{{ $ogImage := "" }}
{{ with .Params.image }}
{{ $ogImage = . | absURL }}
{{ else }}
{{ with .Params.preview }}
{{ $ogImage = . | absURL }}
{{ else }}
{{ with .Params.photo }}
{{ if reflect.IsSlice . }}
{{ range first 1 . }}
{{ if isset . "url" }}
{{ $ogImage = .url | absURL }}
{{ else if isset . "value" }}
{{ $ogImage = .value | absURL }}
{{ else }}
{{ $ogImage = . | absURL }}
{{ end }}
{{ end }}
{{ else if reflect.IsMap . }}
{{ if isset . "url" }}
{{ $ogImage = .url | absURL }}
{{ else if isset . "value" }}
{{ $ogImage = .value | absURL }}
{{ end }}
{{ else }}
{{ $ogImage = . | absURL }}
{{ end }}
{{ else }}
{{ with .Resources.ByType "image" }}
{{ range . }}
{{ if in .Name "feature" }}
{{ $ogImage = .Permalink }}
{{ break }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ if $ogImage }}
<meta property="og:image" content="{{ $ogImage }}" />
{{ end }}
```
## Could I have done this more efficiently?
For sure, I mean anything that requires you to run headless-chrome is not exactly going to be lightweight. I noticed that [this post](https://runtimeterror.dev/dynamic-opengraph-images-with-hugo/) uses Hugo's built in image manipulation features to take an image template and overlay some text onto it rather than booting up a whole browser. I went for lowest barrier to entry/quickest win and I'm not too worried about the resource that running this script once every few days when I hit "publish" is going to require. I'll revisit this if it ever does become an issue or if I feel like deep diving on Hugo's image generation stuff.