This commit is contained in:
parent
2fa6e70af1
commit
6841623e23
17 changed files with 2306 additions and 148 deletions
|
|
@ -128,6 +128,62 @@ build. Neither fails the build on a network error — a fediverse outage leaves
|
|||
previous `posts.json` in place, and a single failed download leaves that one entry
|
||||
pointing at its original URL rather than losing the post.
|
||||
|
||||
### The full body, and its inline media
|
||||
|
||||
Each post is hosted whole at `/posts/<slug>`, with the card on `/posts` linking
|
||||
to it — the writing is what the site is about, and a page this site can name as
|
||||
canonical is the only version a search engine can be pointed at. The comments
|
||||
are *not* mirrored: every post page links out to the thread, which is where the
|
||||
discussion belongs.
|
||||
|
||||
The body stays Markdown in `posts.json` and is rendered by
|
||||
`Catcrafts.Shared:Markdown` at page-render time, never converted to HTML by the
|
||||
shell. That is deliberate: the renderer is inside the escaping guarantee, and
|
||||
text fetched from someone else's server must not be able to become markup
|
||||
anywhere else. Raw HTML in a body is always shown as text.
|
||||
|
||||
`fetch-media.sh` mirrors what the body embeds as well as the headline file, and
|
||||
rewrites the URLs **inside the Markdown**, so a post page loads nothing
|
||||
third-party either. It also writes a `body_media` list per post — dimensions,
|
||||
poster frame and format renditions for each inline file, which Markdown syntax
|
||||
has nowhere to carry. Slugs come from the title; a duplicate title takes the
|
||||
post's numeric id as a suffix, so an old post's URL is never renumbered by a new
|
||||
one. Re-running the script is a no-op: local paths are adopted from the mount
|
||||
rather than re-fetched.
|
||||
|
||||
### The image format ladder
|
||||
|
||||
Every mirrored still image is transcoded to two siblings named after its content
|
||||
hash, and `Catcrafts.Shared:Media` serves all three as one `<picture>` — so the
|
||||
browser fetches **exactly one**:
|
||||
|
||||
| tier | file | size vs. WebP | who gets it |
|
||||
|---|---|---|---|
|
||||
| `<source type="image/avif">` | `<hash>.avif` | **76%** | almost everyone |
|
||||
| `<source type="image/webp">` | `<hash>.webp` (the mirrored original) | 100% | Safari 14–16 |
|
||||
| `<img src>` | `<hash>.png` | **875%** | neither of the above |
|
||||
|
||||
The middle tier is why the PNG being ~9× the WebP does not matter: it is free
|
||||
(the mirror already downloaded that file) and it is what the small number of
|
||||
non-AVIF browsers actually land on. The PNG is the floor nothing can refuse.
|
||||
|
||||
AVIF is encoded at `crf 26, cpu-used 6, yuv444p` — measured at SSIM 0.997
|
||||
against the source and still smaller than it. Full chroma is deliberate: these
|
||||
are screenshots of text, and re-subsampling chroma that pict-rs already
|
||||
subsampled once fringes coloured text visibly, for about 3% more bytes.
|
||||
|
||||
Encoding is skipped when the sibling is already on the mount, so only genuinely
|
||||
new images cost encoder time (~0.5 s each). Animated sources are left alone
|
||||
entirely — one moving GIF beats three copies of its first frame. Video posters
|
||||
are skipped too: `poster` takes exactly one URL, so a `<video>` cannot negotiate
|
||||
a format the way `<picture>` can and the renditions would be unreachable.
|
||||
|
||||
Both encodes pin `-c:v` and then **verify the codec that actually came out**.
|
||||
That check earned its place immediately: `-f image2 out.png` without an explicit
|
||||
codec makes ffmpeg fall back to the muxer default, which is MJPEG — it silently
|
||||
produced a full set of lossy JPEGs under `.png` names, served to browsers as
|
||||
`image/png`. A rendition that fails the check is discarded and its tier dropped.
|
||||
|
||||
### Publish the media first, then post it
|
||||
|
||||
**The recommended flow is to put a recording on catcrafts.net before writing the
|
||||
|
|
|
|||
Loading…
Reference in a new issue