Projector: Keep YouTube Descriptions synced
In my previous post , I used hugo to generate correctly linked, always up to date descriptions for my YouTube Videos.
But if I’m generating the descriptions automatically… I’m hardly going to be excited about copying and pasting them into YouTube - right? right!
Automating this process brings up a few design choices.
Planning
Which language
There were a few contenders, and here’s how I thought them through:
Zig
I’m currently learning Zig , and I love using it for my game development. But it doesn’t yet have mature libraries for working with the YouTube Data API - and I don’t feel like writing one. So, sadly, Zig’s out for this one.
Python
I used Python for despatches and it was the right fit there - good libraries for BlueSky and Reddit.
However, I did not enjoy the experience:
bazelwas a constant strugglepoetryis nice… but still a bit of a nightmare. It just makes the pain more structuredWorst of all: blusky failed after reddit succeeded caused a partial success, which broke the Git commit and silently caused a post to be repeated (embarrassing!)
That kind of problem can happen in Go (nil pointer), though it wouldn’t in Zig. But at least with Go, most handleable errors stay errors — they don’t crash the whole tool.
java
Sure, I could do this in Java - but I really don’t want to mess with the JVM. And more importantly, I’m doing this for fun. Java doesn’t feel like that anymore.
golang
Not quite my favourite any more, but still a close second. It’s fast, has YouTube libraries and it somehow seems fitting that Hugo is also a go baby.
Even though I’m not wiring the two directly, the ecosystem fit is nice.
Overall Plan
Let
Hugorender the YouTube description as plain textTraverse the
youtube/*.mdfiles in the source directory- Skip videos that are too old to update (maybe older than 30 days?)
- Hash the rendered output (title, description, tags, etc.)
- Compare that hash with the one stored in the frontmatter
- If it doesn’t match,
- Update the metadata on YouTube
- Update the hash
- commit and push any updates (should be only hash changes)
Validation
One thing worth being careful about is whether the metadata is valid. We do not want the sync to fail during its scheduled run - when it won’t have many choices on how to resolve it.
In a bid to mitigate this, we’ll add a command to validate the source and rendered files.
The validation would expect the rendered files to be generated as well, which
seems reasonable since Hugo is probably running as hugo serve while the
content files are being updated.
| |
The validate function will retrieve the relevant files and check that there is a corresponding rendered description.
If it errors in that process, we know that it would error out in the sync.
We can’t catch errors around the API though at this stage, and that’s unavoidable.
Sync
Hashing the Description
This part was surprisingly easy:
| |
The challenge was trying to write the updated yaml frontmatter back. I was using
the adrg/frontmatter library to read the frontmatter, but it does not support
writing it back.
Detour: Write a small frontmatter Library
I took a little detour to build inscribe, a little frontmatter library that supports reading and writing back in yaml .
Auth
We need the YouTube Client to have an OAuth Token, which we can retrieve by:
- Create a new OAuth Client - Type of desktop is probably the easiest
- add your user account to test users :
- go to
https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/youtube
- Remember to substitute your actual client_id
- Add any other scopes you might want
- Go through the flow steps - it’ll warn you that the app is unreleased, which is expected
- Take the code that it provides
- Call the following curl command
| |
You should finally get something like:
| |
The refresh_token is what you want to save / use as the access_token will
expire (after an hour in this example).
The authentication was a bit more involved with a refresh token, but the
oauth2 library helps us out:
| |
Updating the description
Setting the description is a little more complicated because you can’t set just the description.
Everything defined in the VideoSnippet gets updated.
To support this, what we need to do is get the current snippet for the video, then update it:
| |
GitHub Action
The GitHub Action is fairly straightforward, mostly a copy of the Hugo one, then:
- Add Bazel
- Run projector sync
- Commit if changed
| |
We also needed to upgrade one permission - contents
| |
Conclusion
The Google/YouTube documentation was the hardest part here in that it was pretty obtuse and hard to understand.
Writing a little frontmatter library was unexpected, but while it took a little time, was straightforward.
Once I got a handle on that, the rest of it was pretty straightforward, partly because I was reusing parts from before.