Files
av-club/manifest.ts
pastilhasandClaude Opus 5 69f4cc9cae av-club claims HEIC for the file browser
`fileTypes` in the manifest, and two GET routes behind it: /v1/thumb and
/v1/render. the platform calls them when it meets a `.heic` and nothing in the
platform knows which plugin answered.

THIS IS THE REASON TO INSTALL av-club FOR SOMEBODY WHO NEVER CONVERTS ANYTHING.
sharp's libheif has no HEVC decoder, so without this a folder of iPhone photographs
is a wall of file icons and opening one offers a download. with it, they thumbnail
and they open.

only heic and heif are claimed. everything else the browser shows — jpeg, png,
webp, avif, svg, and every video ffmpeg reads — core already handles, and claiming
those would take work off a path that is faster and needs no plugin.

size and width are CLAMPED here rather than trusted. the caller is the platform
rather than a browser, but a route that resizes to whatever number arrives is one
query string from an allocation the size of the machine, and "the only caller is
trusted" stops being true the first time somebody adds a second caller.

THE DERIVED ASSET IS DELETED AFTER THE BYTES ARE READ. runSharp without an
outputPath stores its result — right for the API, wrong here: this is a view of a
file that already exists, asked for on every folder listing, and keeping them would
fill the asset store with thumbnails nothing owns and nothing expires. read fully
rather than streamed, because a streamed Bun.file would still be reading when the
delete lands.

jpeg for the thumbnail (small, universal), webp for the render (the viewer wants
the quality back).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 07:51:06 +01:00

115 lines
5.3 KiB
TypeScript

import type { PluginManifest } from '@@/plugins/manifest';
/**
* av-club — sharp, ffmpeg and ffprobe over HTTP, for whoever needs to turn an upload into something a
* browser can serve.
*
* ── No screens, deliberately ──
*
* This is an API and a body of documentation, not a place to click. Its callers are a commander watching
* an upload folder, an agent, or a person with curl. A dock tile would advertise a page that has nothing
* to show — the work happens where the files are, which is not here.
*
* ── One permission, and it is not `personal` ──
*
* Every other permission's paths act on the caller's own data. These act on assets av-club itself issued
* ids for, in its own store, shared by every caller — so it is an ordinary grantable capability rather
* than a per-account one, and an account without it cannot reach the encoder at all.
*/
export const manifest: PluginManifest = {
publisher: 'officerdev',
version: '2026.08.29.01',
platform: '>=1.0.0',
label: 'AV Club',
summary: 'Convert and treat images, video and audio — sharp, ffmpeg and ffprobe behind one API',
tags: ['Development', 'Personal'],
icon: 'Clapperboard',
color: '#8b5cf6',
/*
Declared HERE rather than in a package.json, and that is not a style choice.
A package.json inside a plugin makes a package boundary, and Bun then stops walking up for the
platform's tsconfig — so `@@/sidecar/connect` stops resolving and the sidecar dies on its first
import. That is exactly how this plugin failed on the day it was extracted: it arrived carrying its
own package.json and bun.lock, and both the router and the sidecar could not find the platform.
`npm-deps.ts` collects these across every plugin into `plugins/node_modules/`, so atelier's copy of
sharp is the one used here too rather than a second 50 MB of platform binaries.
*/
/*
── The two the machine must already have, and the one that is easy to miss ──
ffmpeg and ImageMagick are obvious: everything video goes through the first, and the second is how
HEIC gets decoded at all (see sidecar/sharp/decodable.ts — sharp's bundled libheif is built without
the HEVC decoder, so ImageMagick's system libheif does that work).
`libheif-plugin-libde265` is the one nobody would guess, and it is declared here because it cost an
hour to find. libheif is packaged with its CODECS AS SEPARATE PLUGINS: `libheif1` alone parses a
HEIC container and reports its dimensions, and then fails on the pixels with "Unsupported codec".
Ubuntu ships `libheif-plugin-aomdec` (AV1) in the common install and NOT the HEVC one — which is
what every iPhone actually writes.
Observed 2026-08-29: identical libheif 1.17.6 on two hosts, HEIC working on one and not the other,
the only difference being this package. A plugin that silently half-works on some machines is worse
than one that refuses to install, so it is declared rather than left to luck.
No `binary:` that means anything on its own — it is a shared library plugin, not a command. The
binary named is the one that USES it, which is what makes the check meaningful.
*/
/*
The formats the platform cannot read, and which this plugin can.
HEIC is why av-club is worth installing for somebody who never converts anything: sharp's libheif
has no HEVC decoder, so without this a folder of iPhone photographs is a wall of file icons and
opening one offers a download. With it, they thumbnail and they open.
Only HEIC and HEIF are claimed. Everything else the file browser shows — jpeg, png, webp, avif, svg,
and every video ffmpeg reads — core already handles, and claiming those would be taking work away
from a path that is faster and needs no plugin.
*/
fileTypes: {
extensions: ['heic', 'heif'],
thumbnail: '/v1/thumb',
render: '/v1/render',
},
osDependencies: [
{
binary: 'ffmpeg',
reason: 'Every video and audio conversion. Without it, only images work.',
packages: { apt: 'ffmpeg', pacman: 'ffmpeg', dnf: 'ffmpeg', brew: 'ffmpeg' },
},
{
binary: 'convert',
reason: 'Decodes HEIC, which sharp cannot — see sidecar/sharp/decodable.ts.',
/*
No `dnf`. Fedora's package is literally `ImageMagick`, with capitals, and the manifest validator
refuses an uppercase package name — deliberately, since these strings reach a root command line
and the rule is a closed vocabulary rather than a filter.
Omitted rather than worked around: naming `imagemagick` there would be naming a package that does
not exist, which fails at install time on the one distribution it was meant to help. The manifest
documentation says a case like this "wants a conversation, not a regex that already allows it",
and this is that case being left for the conversation.
*/
packages: { apt: 'imagemagick', pacman: 'imagemagick', brew: 'imagemagick' },
},
{
binary: 'convert',
reason: "libheif's HEVC decoder. Without it a HEIC parses and then fails on its pixels.",
packages: { apt: 'libheif-plugin-libde265' },
},
],
npmDependencies: {
sharp: '^0.35.3',
},
permissions: [
{
key: 'av-club',
label: 'AV Club',
description: 'Convert and inspect media through sharp, ffmpeg and ffprobe',
},
],
};