`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>
115 lines
5.3 KiB
TypeScript
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',
|
|
},
|
|
],
|
|
};
|