mirror of
https://github.com/wezm/wezm.net.git
synced 2024-12-18 18:29:54 +00:00
Add XSLT podcast post
This commit is contained in:
parent
531f2fec35
commit
4451d995d4
2 changed files with 179 additions and 0 deletions
179
v2/content/posts/2023/xslt-podcast/index.md
Normal file
179
v2/content/posts/2023/xslt-podcast/index.md
Normal file
|
@ -0,0 +1,179 @@
|
|||
+++
|
||||
title = "Creating a Podcast From a Mastodon Account With XSLT"
|
||||
date = 2023-03-01T18:33:33+10:00
|
||||
|
||||
#[extra]
|
||||
#updated = 2023-01-11T21:11:28+10:00
|
||||
+++
|
||||
|
||||
{% aside(title="Just want the feed?", float="right") %}
|
||||
Here you go:<br>
|
||||
[ATPrewind podcast feed](https://files.wezm.net/aptrewind.rss)
|
||||
{% end %}
|
||||
|
||||
I recently discovered the [ATPrewind account on Mastodon][ATPrewind]. It's an account
|
||||
sharing "gems discovered while re-listening to [@atpfm] from the very first
|
||||
episode. By [@joshua]". ATP is a tech Podcast that's been running for about 10
|
||||
years. Each post (so far) from ATPrewind includes a short clip from the show in the
|
||||
form of a little video.
|
||||
|
||||
This post describes how I was nerd sniped into creating a podcast from the ATPrewind posts.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
It all started when [I posted the following][my-post] on Mastodon:
|
||||
|
||||
> Ahh this ATP Rewind account is gold https://social.tupo.space/@ATPrewind
|
||||
>
|
||||
> Keep up the great work @joshua
|
||||
|
||||
[Kashyap replied][Kashyap]:
|
||||
|
||||
> Indeed! This should also be a podcast 🙃
|
||||
|
||||
This was a great idea and it got me thinking about how to do it with the least
|
||||
amount of effort.
|
||||
|
||||
In this day and age I imagine many programmers would reach for
|
||||
their favourite programming language and code up something to generate a
|
||||
podcast feed (real podcasts are just RSS). Perhaps using the Mastodon API or
|
||||
similar and then work out a way to host their program.
|
||||
|
||||
With [my recent experience with Deno Deploy][deno-deploy] fresh in my mind I
|
||||
considered using it. However I opted for a decidedly late 90s solution: [XSLT].
|
||||
According to Wikipedia "XSLT is a language originally designed for transforming
|
||||
XML documents into other XML documents". The 'originally' refers to fact that
|
||||
you can now generate any text with it, not just XML.
|
||||
Since it was created in the era of "XML ALL THE THINGS", XSL templates are
|
||||
themselves XML documents. It also makes extensive use of [XPath] expressions to
|
||||
select nodes and extract their content.
|
||||
|
||||
### Creating a Podcast Feed
|
||||
|
||||
Every Mastodon account has an RSS feed so I created an XSL template to process
|
||||
the ATPrewind RSS feed and add the missing elements required to turn it into a
|
||||
valid podcast feed. With some help from [this Dr. Drang][drdrang] post this is
|
||||
what I came up with:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:media="http://search.yahoo.com/mrss/">
|
||||
|
||||
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
|
||||
|
||||
<!-- First, get everything. -->
|
||||
<xsl:template match="node() | @*">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="node() | @*"/>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<!-- Update items to include title and enclosure elements. -->
|
||||
<xsl:template match="/rss/channel/item">
|
||||
<item>
|
||||
<xsl:apply-templates select="node()" />
|
||||
<title>Post on <xsl:value-of select="pubDate"/></title>
|
||||
<enclosure url="{media:content/@url}" length="{media:content/@fileSize}" type="audio/mp4; codecs="mp4a.40.2"" />
|
||||
</item>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
```
|
||||
|
||||
XML noise aside this document should be fairly self explanatory. It copies all
|
||||
nodes from the source RSS feed, then processes each `item` element. To each one
|
||||
it adds a `title` element derived from the text "Post on" and the publication
|
||||
date of the post, and an `enclosure` element derived from the `media:content`
|
||||
element.
|
||||
|
||||
I lie a bit by saying that the enclosure MIME type is `audio/mp4;
|
||||
codecs="mp4a.40.2"`. I.e. AAC-LC in MP4 container for the video in each post.
|
||||
Running `curl -L https://social.tupo.space/@ATPrewind.rss | xsltproc
|
||||
podcast.xsl` produces the podcast feed.
|
||||
|
||||
It took a few tries to get it to work but eventually I was able to convince
|
||||
[Overcast] that it was a real podcast. One trick that I'm exploiting here is
|
||||
that the MP4 container for video and audio is the same, the audio only version
|
||||
is just lacking the video stream. I figured that podcast players might still be
|
||||
able to play the video and at least for Overcast it works:
|
||||
|
||||
{{ figure(image="posts/2023/xslt-podcast/overcast-screenshot.png", link="posts/2023/xslt-podcast/overcast-screenshot.png", alt="Screenshot of Overcast showing the podcast 'episodes'.", caption="Screenshot of Overcast showing the 'episodes'.", width=393) }}
|
||||
|
||||
|
||||
### Deployment
|
||||
|
||||
To deploy this contraption I created a Docker image with `curl` and `libxslt`
|
||||
installed:
|
||||
|
||||
```dockerfile
|
||||
FROM wezm-alpine:3.17.2
|
||||
|
||||
# UID needs to match owner of /home/rss/feeds volume
|
||||
ARG PUID=1000
|
||||
ARG PGID=1000
|
||||
ARG USER=rss
|
||||
|
||||
RUN addgroup -g ${PGID} ${USER} && \
|
||||
adduser -D -u ${PUID} -G ${USER} -h /home/${USER} -D ${USER}
|
||||
|
||||
RUN apk --update add curl libxslt
|
||||
|
||||
COPY ./entrypoint.sh /home/${USER}/entrypoint.sh
|
||||
COPY ./podcast.xsl /home/${USER}/podcast.xsl
|
||||
|
||||
WORKDIR /home/${USER}
|
||||
|
||||
USER ${USER}
|
||||
|
||||
VOLUME ["/home/rss/feeds"]
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
```
|
||||
|
||||
I made a small script as the entrypoint to the container:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
trap 'exit' TERM INT
|
||||
|
||||
while true; do
|
||||
curl -L https://social.tupo.space/@ATPrewind.rss | xsltproc podcast.xsl - > /home/rss/feeds/atprewind.rss
|
||||
sleep 3600 # 1 hour
|
||||
done
|
||||
```
|
||||
|
||||
It regenerates the podcast feed each hour. I bind mount the directory for
|
||||
files.wezm.net into the container in my Docker Compose config, which takes
|
||||
advantage of the fact that nginx is already serving that directory.
|
||||
|
||||
Some may argue that the Docker part of this is not in the spirit of, "least
|
||||
amount of effort", but I already have all this set up on my server so adding
|
||||
one more container is very little effort. I've written previously about [my
|
||||
Alpine Linux server][server] if you'd like to read more.
|
||||
|
||||
Once I pushed the Docker image the podcast was live. The URL is:<br>
|
||||
<https://files.wezm.net/aptrewind.rss> if you'd like to subscribe.
|
||||
|
||||
### Future Work
|
||||
|
||||
I whipped all this up before work today and made an assumption that might not
|
||||
always hold: there is a single video media attachment on each post. So far
|
||||
that's true but I should update the XSL template to only try to generate an
|
||||
`enclosure` element if the attachment is present. It would also be a good idea
|
||||
to filter for audio and video only in case a post appears with images.
|
||||
|
||||
For now I shall eagerly look forward to the next post appearing in Overcast.
|
||||
|
||||
[@atpfm]: https://mastodon.social/@atpfm
|
||||
[@joshua]: https://social.tupo.space/@joshua
|
||||
[ATPrewind]: https://social.tupo.space/@ATPrewind
|
||||
[Overcast]: https://overcast.fm/
|
||||
[XPath]: https://www.w3.org/TR/xpath-31/
|
||||
[XSLT]: https://www.w3.org/TR/xslt-30/
|
||||
[deno-deploy]: https://www.youtube.com/watch?v=d-tsfUVg4II
|
||||
[drdrang]: https://leancrew.com/all-this/2022/08/filtering-my-rss-reading/
|
||||
[server]: https://www.wezm.net/technical/2019/02/alpine-linux-docker-infrastructure/
|
||||
[my-post]: https://mastodon.decentralised.social/@wezm/109940341596949214
|
||||
[Kashyap]: https://mastodon.social/@kgrz/109942702497796855
|
BIN
v2/content/posts/2023/xslt-podcast/overcast-screenshot.png
Normal file
BIN
v2/content/posts/2023/xslt-podcast/overcast-screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 191 KiB |
Loading…
Reference in a new issue