Blurhash placeholders for AP photo-post attachments#159
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds Blurhash support to FOSSE so outbound ActivityPub image attachments include attachment[].blurhash, enabling Pixelfed/Mastodon to render colored-blur placeholders while images load. This fits into FOSSE’s federation “projectors” by computing/storing the hash at upload time (cron) and injecting it during ActivityPub attachment rendering.
Changes:
- Introduces
Automattic\Fosse\Blurhashto compute/store blurhashes for image attachments and inject them viaactivitypub_attachment. - Adds
Automattic\Fosse\Blurhash_CLI(wp fosse blurhash backfill) for backfilling older media. - Adds a comprehensive PHPUnit suite covering storage, scheduling/cron behavior, encoding success/failure paths, and filter injection.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/php/BlurhashTest.php | New PHPUnit coverage for blurhash storage, encoding, cron scheduling, and ActivityPub injection. |
| src/class-photo-post.php | Updates docblock to reflect blurhash now handled by the dedicated Blurhash projector. |
| src/class-blurhash.php | New blurhash implementation: resolve source image, encode via GD + kornrunner/blurhash, store in postmeta, inject into AP attachments. |
| src/class-blurhash-cli.php | New WP-CLI command to backfill/force recompute blurhashes for existing attachments. |
| fosse.php | Registers Blurhash and Blurhash_CLI on init, consistent with other projectors/bridges. |
| composer.json | Adds kornrunner/blurhash dependency. |
| composer.lock | Locks the newly added blurhash dependency. |
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs DOTCOM-17159.
Follow-up to the photo-post federation umbrella (DOTCOM-17143) — the last user-visible gap between FOSSE photo posts and native Pixelfed/Mastodon uploads.
Summary
Compute a Blurhash placeholder string at upload time and add it to outbound ActivityPub
attachment[].blurhash, so Pixelfed and Mastodon paint the colored-blur preview while the full image loads. Without it, a Pixelfed grid mixing native uploads and our federated WP photos shows grey/empty cells where the native uploads paint instantly with color.AT-side (Bluesky, Pinkleap) is unaffected —
app.bsky.embed.imagesdoesn't carry blurhash; PR 156 already emitsaspectRatiofor layout reservation on those clients.What it does
A new
Blurhashclass hooks three things:wp_generate_attachment_metadata— for image attachments, schedule a single-event cron (fosse_blurhash_compute) to encode the hash. Upload UI returns immediately; encoding doesn't block the publish flow. Idempotent so re-uploads / metadata regens don't double-queue.fosse_blurhash_computecron action — call the encoder, store the result as attachment postmeta (_fosse_blurhash).activitypub_attachmentfilter — when an image attachment has a stored hash, addblurhashto the array. No-op for non-image types, missing meta, or malformed arrays.A second class,
Blurhash_CLI, registerswp fosse blurhash backfill(with--dry-run,--limit=N,--force) for sites with media uploaded before this feature was active. Registration is gated onWP_CLIso no overhead on web requests.Architecture notes
[r,g,b][][]pixel array, and GD'simagecreatefrom*family is the only host-portable way to build one. Imagick has its own surface; not handled here. Sites without GD just don't get blurhash placeholders — federation is unaffected. Same posture for any other failure (unreadable file, encoder exception, deleted attachment): never blocks upload, never blocks federation.kornrunner/blurhash(MIT, pure PHP, ~1.8M Packagist downloads, no extension reqs of its own). The algorithm itself is well-defined by the Wolt spec — porting it inline buys very little and costs forever-maintenance of DCT code we'd otherwise never touch.activitypub_attachment(per-attachment) rather thanactivitypub_attachments(per-post). Non-photo posts that happen to include an image (rare today, common tomorrow) get the field too.Files
src/class-blurhash.php— encoder + storage + hooks.src/class-blurhash-cli.php—wp fosse blurhash backfillcommand.tests/php/BlurhashTest.php— 28 cases covering storage helpers, encoder happy + failure paths (corrupt bytes, missing file, non-image, deleted attachment), scheduler idempotency, cron handler, AP injection happy + every defensive no-op, end-to-end round trip.fosse.php— register both classes oninit(same posture as the sibling projectors).src/class-photo-post.php— docblock update;blurhashis no longer scoped out.composer.json/composer.lock—kornrunner/blurhash ^1.2.Out of scope (separate tickets when needed)
Test plan
composer test-php— full suite 704 tests / 1568 assertions pass (28 new inBlurhashTest).composer lint-php— clean._fosse_blurhashpostmeta gets populated.attachment[0].blurhashis present.wp fosse blurhash backfill --dry-runon a site with pre-existing media, confirm it reports the expected count. Re-run without--dry-runand verify postmeta lands.