added missing icons
All checks were successful
Deploy / build-deploy (push) Successful in 3m0s

This commit is contained in:
Jorijn van der Graaf 2026-08-18 21:21:41 +02:00
commit faf881fa0c
10 changed files with 201 additions and 8 deletions

View file

@ -1,6 +1,6 @@
#!/bin/sh
# Publish a recording or screenshot to catcrafts.net and print the URL to put in
# the fediverse post.
# Publish a recording, screenshot or audio file to catcrafts.net and print the
# URL to put in the fediverse post.
#
# usage: tools/publish-media.sh [-r] [-c CRF] <file>...
#
@ -40,6 +40,24 @@
# with no <source> negotiation in front of it — and it must be the encoding
# everything can play. Our own pages recognise the .h264.mp4 name, serve the AV1
# to browsers that can take it, and keep the H.264 as the <source> fallback.
#
# AUDIO: the same two rails, for the same reason. Opus in WebM is the efficient
# one — the first file through here, 93 s of speech, went from a 1.5 MB 128k MP3
# to 553 KB — and an MP3 sibling is the one that plays everywhere, so the MP3 is
# the URL printed for the post. The primary does not have to be universal
# precisely because the sibling is.
#
# Two ways the audio path deliberately differs from the video path above. It does
# not fold everything to mono: here the audio IS the content rather than a
# rounding error next to a picture, so the source's channel layout survives and
# sets the bitrate. And a source that is ALREADY an MP3 is copied to the
# compatibility rail untouched rather than re-encoded — MP3 to MP3 spends a
# second generation of lossy artefacts to arrive at the same rail, and speech,
# which is most of what gets published here, is where that is most audible.
#
# Nothing downstream builds an <audio> element yet: fetch-media.sh does not match
# these names and the post renderer has no audio case. So the printed URL is for
# posting and for linking by hand, not yet for embedding in a post body.
set -eu
@ -116,10 +134,12 @@ for src in $FILES; do
case "$ext" in
mp4|webm|mov|mkv|avi) kind=video ;;
png|jpg|jpeg|webp|gif|avif) kind=image ;;
mp3|m4a|aac|opus|oga|ogg|flac|wav|wma|aiff) kind=audio ;;
esac
poster=""
fallback=""
fallback_suffix=""
if [ "$RAW" = 1 ] || [ "$kind" = other ]; then
payload="$src"
out_ext="$ext"
@ -137,6 +157,7 @@ for src in $FILES; do
# the players most likely to be shaky about AV1 anyway, and the audio is
# a rounding error next to the video either way.
fallback="$WORK/h264.mp4"
fallback_suffix=h264.mp4
echo " transcoding H.264 fallback (crf 23)…"
# Same source and same denoise as the AV1, so the two encodings show the
# same frames. CRF is x264's own scale (not comparable to SVT-AV1's);
@ -156,6 +177,42 @@ for src in $FILES; do
at=$(awk -v d="$dur" 'BEGIN { printf "%.2f", (d > 3 ? d / 3 : 0) }')
ffmpeg -nostdin -v error -ss "$at" -i "$payload" -frames:v 1 \
-vf "scale=720:-2" -c:v libwebp -quality 80 "$poster" -y
elif [ "$kind" = audio ]; then
payload="$WORK/out.webm"
out_ext=webm
fallback_suffix=mp3
# Channel count sets the bitrate instead of being flattened by it: 48k of
# mono Opus is comfortable for speech, and stereo music at that figure
# would not be. These are VBR targets, not ceilings, and libopus does not
# track them evenly — on the first file through here the 48k target came
# out at 45 kbps while 64k ran away to 76, which is why the mono rail sits
# at the lower figure rather than splitting the difference.
ch=$(ffprobe -v error -select_streams a:0 -show_entries stream=channels \
-of default=nw=1:nk=1 "$src" </dev/null 2>/dev/null \
| head -n1 || true)
case "$ch" in ''|*[!0-9]*) ch=1 ;; esac
if [ "$ch" -gt 1 ]; then ab=96k; else ab=48k; fi
echo " transcoding to Opus ($ab, ${ch}ch)…"
# -vn drops cover art, which arrives as a still video stream and belongs
# in neither output — without it an MP3 carrying album art fails the
# encode outright. -map_metadata -1 drops the ID3 tags, which on a file
# from someone else carry their tooling and their file naming. 48 kHz is
# the rate Opus works at internally, so saying it makes the resample of
# the usual 44.1 kHz source explicit rather than implied.
ffmpeg -nostdin -v error -i "$src" -vn -map_metadata -1 \
-c:a libopus -b:a "$ab" -ar 48000 \
"$payload" -y
fallback="$WORK/fallback.mp3"
if [ "$ext" = mp3 ]; then
echo " MP3 fallback: source is already MP3, copied unchanged"
cp "$src" "$fallback"
else
echo " transcoding MP3 fallback (128k)…"
# Fixed 128k, decodable by anything, and the same "bigger is
# acceptable on the compatibility rail" trade the H.264 above makes.
ffmpeg -nostdin -v error -i "$src" -vn -map_metadata -1 \
-c:a libmp3lame -b:a 128k "$fallback" -y
fi
else
payload="$WORK/out.webp"
out_ext=webp
@ -171,17 +228,19 @@ for src in $FILES; do
# their own. That is the whole point: fetch-media.sh finds both by name,
# with nothing to look up — the poster when the instance made no thumbnail
# (pict-rs will not read AV1, so for these uploads that is the normal
# case), the fallback for every browser that cannot decode AV1.
# case), the fallback for every browser that cannot decode AV1. Audio's MP3
# sibling is named the same way, after the Opus file's hash.
if [ -n "$poster" ] && [ -f "$poster" ]; then
publish_file "$poster" "$hash.poster.webp"
fi
post_name="$name"
if [ -n "$fallback" ] && [ -f "$fallback" ]; then
post_name="$hash.h264.mp4"
post_name="$hash.$fallback_suffix"
publish_file "$fallback" "$post_name"
fi
# The postable URL is the H.264 one when it exists — see FALLBACK above.
# The postable URL is the compatibility rail when there is one: the H.264
# video or the MP3 audio — see FALLBACK and AUDIO above.
url="$SITE_ORIGIN/media/$post_name"
# Confirm it is actually being served before handing over a URL that is about
# to be pasted into a post, where a 404 is public and permanent.