mcpField
C
crabcrop

crabcrop

本地服务
github.com/dayvson媒体与设计3今天更新

关于 crabcrop

crabcrop 是用 Rust 编写的图像优化服务,面向需要以多种尺寸提供图像的网站和应用。它根据 URL 中编码的操作按需处理图像,并支持签名请求和缓存结果。

主要功能

  1. 01调整尺寸、裁剪和转换图像
  2. 02使用 HMAC-SHA256 签署 URL
  3. 03设置 URL 过期时间和缓存时长
  4. 04从 HTTP、S3、GCS 或本地文件加载图像
  5. 05清除缓存结果

适用场景

  1. 01从单个源图像提供多种尺寸
  2. 02通过签名保护图像处理 URL
  3. 03为网站和应用缓存处理后的图像

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"crabcrop-crabcrop": {
"command": "npx",
"args": [
"-y",
"@crabcrop/mcp"
],
"env": {
"CRABCROP_BASE": "<CRABCROP_BASE>",
"CRABCROP_SECRET": "<CRABCROP_SECRET>"
}
}
}
}
启动命令npx -y @crabcrop/mcp
需要配置的环境变量
CRABCROP_BASEBase URL of the crabcrop server the tools act on (for example https://images.example.com).
CRABCROP_SECRET密钥The server's signing secret, used by sign_url and purge_result. Keep it in the environment, not in tool arguments.

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@crabcrop/mcp0.1.0

工具

4

收录的工具列表 · 更新于 5 天前

参数类型必填说明
basestring否crabcrop 端点的源站。默认值为 CRABCROP_BASE。
keyIdstring否可选的密钥 ID,会写入签名段(`<key-id>:<tag>`),供运行密钥轮换的服务器使用;此类服务器只会使用指定密钥验证带密钥的签名段。
secretstring否签名密钥。默认值为 CRABCROP_SECRET;省略则生成不安全 URL。此参数包含敏感信息且已弃用:建议使用 CRABCROP_SECRET 环境变量;逐次调用传入的 secret 会保留在客户端记录中。
sourcestring是源定位符:http(s) URL、s3://bucket/key、gs://bucket/key 或 file:// 路径。
expiresnumber否可选的 Unix 秒级过期时间,会以 exp: 段的形式输出。
operationsstring是操作字符串,例如 "crop:1200x630:smart/filters:format(webp)";可以为空,也可以包含 preset: 引用。

详细介绍

crabcrop

crabcrop 吉祥物:一只挥手问好的钩针编织螃蟹,手里拿着一张照片

crabcrop 是一款使用 Rust 编写的按需图像优化服务。你可以在 URL 中编码操作来请求图像,例如调整大小、裁剪、转换和滤镜;crabcrop 会获取源图像,按顺序应用这些操作,然后返回结果。无需预生成缩略图,也无需构建步骤:URL 就是 API。

crabcrop 适用于需要以多种尺寸提供相同图像,并希望在源图像存储前端使用经过签名且可缓存的图像 URL 的网站和应用。

安装

Shell 安装程序(macOS 和 Linux):

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dayvson/crabcrop/releases/latest/download/crabcrop-installer.sh | sh

**Homebrew:**从最新版本下载 crabcrop.rb,然后运行:

brew install --formula ./crabcrop.rb

**Docker:**基础镜像,以及内置背景移除和人脸检测功能的 AI 版本:

docker pull ghcr.io/dayvson/crabcrop:0.1.0
docker pull ghcr.io/dayvson/crabcrop-ai:0.1.0
docker run -e CRABCROP_SECRET=your-secret -p 3000:3000 ghcr.io/dayvson/crabcrop:0.1.0

**发布版二进制文件:**macOS 和 Linux 的预构建 tar 包支持 x86_64 和 arm64,可在发布页面获取。若要改为从源代码构建,请参阅部署。

快速开始

**1. 启动服务。**必须提供密钥。本地开发时,还需允许未签名的 URL:

CRABCROP_SECRET=local-dev-secret CRABCROP_ALLOW_UNSAFE=1 cargo run --release
# crabcrop listening on port 3000

**2. 请求图像。**这会将源照片调整为 300×200:

curl "http://localhost:3000/unsafe/300x200/https://example.com/photo.jpg" --output photo.jpg

将 example.com/photo.jpg 替换为你拥有的图像 URL。你会收到一张 300×200 的 JPEG 图像。

**3. 在生产环境中为 URL 签名。**移除 CRABCROP_ALLOW_UNSAFE,并将 HMAC 签名放在第一个路径段中:

curl "http://localhost:3000/3c1f6c180116661877bd4c85fd201a178087d74c1cc188cc584e819d901308c6/300x200/https://example.com/photo.jpg" --output photo.jpg

该签名是使用密钥 your-secret 对 300x200/https://example.com/photo.jpg 计算得到的 HMAC-SHA256。请参阅为请求签名。

请求的工作方式

每个图像请求都由三部分组成:

GET /<signature>/<operations>/<source-url>
部分说明
signature对其后所有内容计算得到的 HMAC-SHA256(十六进制),或字面值 unsafe。
operations零个或多个操作段,按路径中的顺序应用。
source-url源图像的完整 URL,包括协议。它可以包含斜杠;第一个不是操作的路径段即为源 URL 的起始部分。
/unsafe/crop:1200x630:smart/filters:grayscale:quality(80)/https://example.com/photo.jpg
为请求签名

使用你的 CRABCROP_SECRET 作为密钥,对路径剩余部分(即操作和源 URL)计算 HMAC-SHA256。内容须与实际显示完全一致,且不带开头的斜杠,然后将结果编码为十六进制:

printf '%s' '300x200/https://example.com/photo.jpg' | openssl dgst -sha256 -hmac 'your-secret'
# SHA2-256(stdin)= 3c1f6c180116661877bd4c85fd201a178087d74c1cc188cc584e819d901308c6

将该十六进制字符串用作第一个路径段。任何人都可以使用以下测试向量验证该算法:密钥为 Jefe 时,what do ye want for nothing? 的 HMAC-SHA256 为 ba5396e10039ec002bf1fde117eee48df8c2f06bc47b5f2d3272ea77f6862da9。

签名无效的请求会返回 403,响应正文为 bad signature。字面签名 unsafe 会跳过验证,但仅当服务以 CRABCROP_ALLOW_UNSAFE=1(或 true、yes、on)运行时才有效。生产环境中请关闭此选项。清除缓存是例外,始终必须使用密钥生成的 HMAC 签名,不能绕过:即使在不安全模式下,/purge 和 /purge-group 也始终要求签名。

有效期 URL

在操作中任意位置添加 exp:<unix-seconds> 段,即可设置签名 URL 的过期时间:

/<signature>/exp:1893456000/300x200/https://example.com/photo.jpg

该段位于签名覆盖的路径剩余部分中,因此 HMAC 会包含它:没有密钥就无法延长过期时间而不破坏签名。超过过期时间后,请求会返回 403,响应正文为 signature expired;已过期的 URL 永远不会从缓存中提供。过期时间的比较精确:URL 仅在其 exp 秒之前有效,系统不会内置允许时钟偏差的余量。请保持服务器时钟同步(NTP),并根据自身时钟情况为过期时间留出所需余量。没有 exp 的 URL 不会过期,与此前完全相同。

每次请求的缓存时长

客户端和 CDN 会将图像响应缓存一年:Cache-Control: public, max-age=31536000, immutable,因为签名 URL 的字节内容永远不会改变。若某个响应需要较短的缓存时长,可在操作中任意位置添加 maxage:<seconds> 段:

/<signature>/maxage:60/300x200/https://example.com/photo.jpg

该值范围为 0 到 31536000。该请求的响应会携带 Cache-Control: public, max-age=60, immutable,而不是一年的缓存时长;其他响应不受影响。maxage:0 会返回 Cache-Control: public, max-age=0,且不带 immutable:响应到达时即已过期,因此缓存必须先重新验证,才能再次使用。

该指令只会更改响应头,不会进入缓存键。因此,仅 maxage 不同的 URL 会共用同一张已存储图像,清除缓存的行为也不受影响。共享结果缓存(S3)存储的是响应正文,而非响应头,因此每个响应的缓存时长也会在此处生效:无论响应正文来自何处,构建响应时都会应用对应请求的缓存时长。

与 exp 一样,该段位于签名部分中,重复出现时以最后一个为准。格式错误或超出范围的值会导致 400,不会静默回退到默认值。预设默认仍为不可变;在预设 URL 中添加 maxage 是调用方对该响应的明确选择,而预设定义不能包含它(它属于每次请求的元数据,与 exp 一样)。

源图像

加载器会在固定限制内获取所有类型的源图像:

  • 每次获取总时长为 10 秒,包含重定向所用时间。
  • 正文大小上限为 10 MiB;超过上限的源会失败并返回 502(请求本身没有问题;是源对象过大)。
  • 如果 HTTP 响应带有 Content-Type 头,该值必须以 image/ 开头。

解码也有上限,因此小文件无法膨胀成数 GB:每个轴最多 16384 px,解码器分配的内存最多为 256 MiB;动画 GIF 和 WebP 源最多 200 帧,解码后的像素总数最多为 1 亿。超出限制的源会失败并返回 502。

HTTP 和 HTTPS 源还允许最多 5 次重定向,并会在每次跳转时重新检查源策略。

另外三种源类型用于获取不在公共 URL 后的原始图像:

  • s3://bucket/key:从 AWS S3 获取;如果设置了 CRABCROP_S3_ENDPOINT,也可从兼容 S3 的存储(MinIO、R2、Spaces)获取。当环境中存在 AWS 凭证(AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY,以及可选的 AWS_SESSION_TOKEN)时,请求会使用 SigV4 签名;如果没有凭证,请求将不签名,公共存储桶可直接使用。
  • gs://bucket/key:从 Google Cloud Storage 的兼容 S3 的 XML API 获取;如果设置了 HMAC 凭证对 CRABCROP_GCS_ACCESS_KEY / CRABCROP_GCS_SECRET_KEY,请求会使用该凭证对签名。
  • file:///path:获取 CRABCROP_FILE_ROOT 下的本地文件。每次请求都会规范化路径,并确保路径仍在根目录下,因此会拒绝 .. 段和逃逸根目录的符号链接。未设置根目录时会禁用文件源。

存储桶源受 CRABCROP_ALLOWED_BUCKETS 限制,而非主机策略,因为端点由运维人员配置,并非请求输入。配置签名凭证时,必须设置允许列表:若不设置,服务器将拒绝启动,因为签名请求可能会访问凭证可读取的所有存储桶。列出 * 可有意允许所有存储桶。如果存储桶或对象键包含点段(路径组件 . 或 ..),则会被拒绝,因为请求路径可能因此越过允许列表中的存储桶;段名称内部包含点则不受影响。存储桶名称还必须遵循 S3 命名规则(3 到 63 个字符,由小写字母、数字、点和连字符组成,以字母或数字开头和结尾,不含相邻的点,也不能是 IP 地址形式):名称会被插入请求主机中,因此在解析时会拒绝包含 ? 或其他会破坏主机名的字符的名称,不论允许列表如何设置。存储服务响应不会重定向:存储服务返回的 3xx 会以其状态码呈现。 可解码 JPEG、PNG、WebP、GIF 和 JPEG XL 源文件。任何操作运行前都会应用 EXIF 方向信息,因此手机照片会以正向显示。输出格式为 GIF 时,动画 GIF 会保留帧;将动画 GIF 转换为静态格式时,会使用第一帧。输出格式为 WebP 或 GIF 时,动画 WebP 会保留帧,并保留帧延迟和循环次数;将其转换为静态格式时,会使用第一帧。动画 WebP 输出始终采用无损编码:纯 Rust WebP 编码器没有有损动画模式,因此质量设置不适用于它(GIF 输出也一样)。包含 AVIF 解码功能的构建版本可解码 AVIF 源文件,包括 Docker 镜像、发布二进制文件和 Homebrew 安装(该安装包提供发布二进制文件)。源代码构建可通过 --features avif-decode 启用该功能,此时会链接 dav1d(系统 libdav1d 1.3 或更高版本,或 vendored 构建);未启用时,AVIF 源文件会返回 502,并显示指明该功能的错误。动画 AVIF 源文件会解码其第一帧。所有构建版本都通过纯 Rust oxideav 栈(oxideav-heif + oxideav-h265)解码 HEIC/HEIF 源文件:仅处理主图像,并解析网格和叠加合成、遵循 EXIF 方向信息,并将 10-bit 源文件转换到流水线的 8-bit 工作色彩空间,与 AVIF 源文件一样。

支持 SVG 源文件,并根据内容而非声明的内容类型检测。渲染前会对 SVG 进行清理:剥离脚本元素、事件处理程序和外部资源引用,因此源 SVG 只能绘制其自身包含的内容。清理后的文档会按 URL 请求的尺寸(或 URL 未请求尺寸时的内在尺寸)栅格化,然后进入常规流水线。SVG 源文件的默认输出格式为 PNG。

操作

操作会按其在路径中出现的顺序运行。请求的输出每个轴最多为 16383 px,总像素数最多为 100 megapixels;超过任一限制的请求会在分配任何内容之前返回 400。

调整大小
<width>x<height>
形式结果
300x200精确输出为 300×200 px。
300x0 或 300x宽度为 300 px,高度按比例缩放。
0x200 或 x200高度为 200 px,宽度按比例缩放。
0x0原始尺寸。
origx100源宽度,高度为 100 px。orig 可用于任一轴。
-300x200调整为 300×200,然后水平翻转。
300x-200调整为 300×200,然后垂直翻转。
curl "http://localhost:3000/unsafe/300x0/https://example.com/photo.jpg"
适配到框内
fit-in:<width>x<height>[:adaptive]

按比例缩放图像,使其适配框内。轴值为 0 表示不设限。此处不允许翻转。

使用 :adaptive 时,如果源图像的方向与框的方向不一致,框的轴会互换:纵向照片适配到横向框时,会改为适配互换后的纵向框,因此不会受到较紧轴的挤压。方向一致时,adaptive 不会改变任何内容。正方形源图像、正方形框,以及轴值为 0 或 orig 的框都没有方向差异,因此不会互换。

curl "http://localhost:3000/unsafe/fit-in:800x600/https://example.com/photo.jpg"
curl "http://localhost:3000/unsafe/fit-in:800x600:adaptive/https://example.com/photo.jpg"
填充为精确尺寸
pad:<width>x<height>[:<fill>]

将图像适配到框内(遵循 fit-in 规则,包括 no_upscale),使其居中,并填充画布剩余部分,使输出尺寸精确等于请求的尺寸。pad: 不支持 adaptive 形式:画布尺寸是明确指定的,因此不会互换。必须以像素指定两个维度:不接受按比例缩放的轴值(0)、orig 和翻转尺寸,这与 crop: 的精确框契约相同。填充内容可以是 6 位十六进制颜色,也可以是用于环境条的 blur:将图像副本按 cover 方式缩放并大幅模糊,这是固定宽高比区域中用户生成内容的标准处理方式。如果未指定填充内容,则使用请求的 fill(...) 滤镜颜色;若未指定,则使用黑色。

curl "http://localhost:3000/unsafe/pad:1200x630:blur/https://example.com/photo.jpg"
按宽高比裁剪
crop:<width>x<height>[:<align>]

将图像裁剪为指定的宽高比,然后调整为精确的宽度和高度。两个尺寸都必须以像素为单位:比例轴(0)、orig 和翻转尺寸在解析 URL 时都会被拒绝(比例框由 fit-in: 处理,翻转由 filters:flip 处理)。宽高比不同时,align 决定保留哪一部分:

对齐方式保留内容
center中间部分。这是默认值。
top顶边。
bottom底边。
left左边。
right右边。
top-left左上角:裁剪宽度时保留左边,裁剪高度时保留顶边。
top-right右上角。
bottom-left左下角。
bottom-right右下角。
smart以焦点为中心的窗口:配置了人脸模型时,焦点为检测到的人脸(参见 AI 功能);否则为图像中局部细节最丰富的位置。检测到人脸时,窗口会按需移动以完整保留人脸:优先选择能完整包含所有人脸的位置;否则选择能完整保留最多人脸区域的位置(较大的人脸优先);如果人脸大于窗口,则让人脸保持居中。debug:focal 背后的裁剪记录会统计检测到的人脸数量,以及窗口完整保留的人脸数量。
face以检测到的最大人脸为中心的窗口,并按 facepad(下文所述)扩展边距。需要人脸模型;没有检测到人脸(或检测失败)时,会回退到 smart 窗口。
curl "http://localhost:3000/unsafe/crop:1200x630:top/https://example.com/photo.jpg"
裁剪矩形区域
crop-rect:<left>x<top>:<right>x<bottom>

精确裁剪两个角之间的像素矩形。right 必须大于 left,bottom 必须大于 top。矩形超出源图像范围时会返回 400 错误。

curl "http://localhost:3000/unsafe/crop-rect:100x50:700x450/https://example.com/photo.jpg"
焦点矩形
focal:<left>x<top>:<right>x<bottom>

指定 URL 中下一个 crop: 必须保留的源像素矩形。此片段本身不会产生输出;裁剪操作会使用它。center 或 smart 裁剪会将窗口定位在该矩形上:以矩形中心为中心,并限制窗口位置,使能放入窗口的矩形完整保留在其中。存在待处理的焦点矩形时,smart 会完全跳过人脸和细节检测,因为调用方已指定关注内容。方向对齐方式(top、bottom、left、right 和四个角)以及 face 都是明确的独立指令,会忽略该矩形。right 必须大于 left,bottom 必须大于 top。矩形超出源图像范围时会返回 400 错误。坐标对应源像素,不会按 dpr 缩放。

curl "http://localhost:3000/unsafe/focal:100x50:700x450/crop:800x600:smart/https://example.com/photo.jpg"
调试视图
debug:focal

渲染 smart-crop 调试视图:响应不是变换结果,而是一张诊断图像,显示 URL 中下一个 crop: 操作所处理的画布,并在图上绘制该裁剪操作实际作出的决定。洋红色十字线标记选定的焦点;如果存在焦点矩形(调用方提供的 focal: 矩形,或 face 裁剪所居中的人脸),则会用洋红色描绘其轮廓。参与决策的检测人脸框为琥珀色,裁剪窗口轮廓为蓝色。线宽和十字线尺寸会随图像缩放。URL 中没有裁剪操作时,视图会显示最终图像的焦点分析。未配置 AI 模型时,仅由显著性分析驱动;任何其他 debug: 值都会返回 400 错误。

调试响应绝不会存储在任何缓存层中,也不会从缓存层提供,并且会附带 Cache-Control: no-store:每次都会根据请求本身的处理过程计算诊断结果。由于该视图会运行人脸和焦点提供程序,debug:focal 请求会像其运行的 AI 工作一样获取 AI 闸门许可。动画源图像,或解析后的格式为视频格式的处理链,会返回 400 错误,而不会提供仅部分应用的调试视图。

curl "http://localhost:3000/unsafe/debug:focal/crop:800x600:smart/https://example.com/photo.jpg"
滤镜
filters:<name>(<params>):<name>(<params>):...

同一个 filters: 段内的过滤器以 : 分隔,并按书写顺序运行。不带参数的过滤器可以省略括号:grayscale 和 grayscale() 等效。也可以在路径中重复使用 filters: 段。指令重复时,以最后一个为准。

像素过滤器会改变图像本身:

FilterParametersDescriptionExample
grayscale无转换为灰度图像。filters:grayscale
invert无反转每个通道。filters:invert
blurblur(2.5),sigma,最大为 512 的正数高斯模糊。filters:blur(2.5)
brightenbrighten(40),−255 到 255 之间的整数,添加到每个通道并限制在 0–255调亮(负值则调暗)。filters:brighten(40)
contrastcontrast(25),百分比,范围 −100 到 100;0 表示不变,−100 表示纯灰,正值会提高对比度调整对比度。filters:contrast(25)
huerotatehuerotate(90),角度,整数旋转色相。filters:huerotate(90)
sharpensharpen(1.5),强度,正数使用反锐化蒙版进行锐化。filters:sharpen(1.5)
saturationsaturation(1.5),范围 0 到 255;0 表示灰度,1 表示不变调整色彩饱和度。filters:saturation(0.5)
rgbrgb(10,-5,0),各通道偏移量,每个值范围 −255 到 255为红、绿、蓝通道添加偏移量。filters:rgb(10,-5,0)
curvecurve(rgb,0,0,255,255),通道(rgb、r、g 或 b),然后是 2 到 16 个 x,y 控制点,每个值为 0–255,且 x 严格递增通过控制点重新映射色调,点之间采用线性插值;输入超出点的 x 范围时,会限制为最近端点的值。重复使用此滤镜可分别调整各通道。filters:curve(r,0,64,255,255)
convolutionconvolution(0,-1,0,-1,5,-1,0,-1,0,0),按行优先顺序排列的 9 个(3×3)或 25 个(5×5)卷积核值,每个值为 −1024 到 1024 之间的普通小数,然后是归一化标志(0 或 1)在整张图像上应用一次卷积核;边缘采用复制方式处理。启用标志后,会先按卷积核各项之和缩放(全 1 卷积核会取平均值);如果总和为 0,则按原样应用卷积核。filters:convolution(0,-1,0,-1,5,-1,0,-1,0,0)
noisenoise(40),强度,0 到 100 之间的整数,表示满量程的百分比单色胶片颗粒效果:每个像素一个噪声值,在满量程的 ±strength 百分比范围内均匀分布,并等量添加到红、绿、蓝通道(alpha 不变)。噪声场是确定性的,根据图像内容和强度生成种子,因此相同请求始终会返回相同字节,并能可靠缓存。noise(0) 不会改变图像。对于动画源,会逐帧应用,并根据每帧自身的内容生成种子,因此各帧的颗粒效果互不相关。filters:noise(40)
equalize无按通道进行直方图均衡化。filters:equalize
rotaterotate(45),任意角度(以度为单位),允许小数;允许负值旋转图像。90 的倍数会精确旋转四分之一圈;其他角度会扩展画布以容纳图像,未覆盖的角落会填充 fill 颜色(默认为白色),如果图像带有 alpha 通道则填充透明色。filters:rotate(-90)
trimtrim 或 trim(20),容差范围 0–255,默认为 10裁剪掉与左上角颜色在容差范围内匹配的边框。filters:trim(20)
sepia无经典的暖调棕褐色效果。filters:sepia
duotoneduotone(1a1a40,ffd9a0),两种颜色,每种由六位十六进制数字表示根据亮度将阴影映射为第一种颜色,将高光映射为第二种颜色。filters:duotone(1a1a40,ffd9a0)
monochromemonochrome(ff8000),六位十六进制数字单色渲染:将每个像素的亮度乘以指定颜色,因此满亮度会精确呈现该颜色。filters:monochrome(ff8000)
pixelatepixelate(8),像素块大小,以像素为单位,至少为 1将图像马赛克化为方形像素块,每个像素块以其中像素的平均颜色填充。filters:pixelate(8)
flipflip(h) 或 flip(v)在处理链的当前位置水平或垂直镜像图像。filters:flip(h)
blur_regionblur_region(x,y,w,h),像素对一个矩形区域进行高斯模糊(车牌、屏幕、文档)。坐标对应处理链在当前位置的图像;模糊强度会随区域大小变化。区域超出图像范围时会报错,而不会只遮盖部分区域。filters:blur_region(120,40,180,60)
pixelate_regionpixelate_region(x,y,w,h),像素将一个矩形区域像素化,边界规则与 blur_region 相同。像素块大小会随区域大小变化。filters:pixelate_region(120,40,180,60)
radiusradius(24),像素;或使用四个值 radius(24,24,0,0),顺序与 CSS 圆角顺序相同(左上、右上、右下、左下)将圆角平滑处理。半径大于画面尺寸时会按比例缩小,以避免圆角重叠。输出格式必须支持 alpha:png、webp、jxl 或 avif。filters:radius(24):format(png)
borderborder(4,000000),宽度(以像素为单位),颜色使用六位十六进制数字表示在画面内侧绘制一条不透明的色带。尺寸不会改变。适用于任何格式。filters:border(4,000000)
maskmask(circle) 或 mask(ellipse)仅保留内切形状(圆形的直径为较短边);其余部分变为透明。输出格式必须支持 alpha。filters:mask(circle):format(png)
curve 滤镜是精细的色调控制,其他加法滤镜只能近似实现。每个控制点表示“输入 x 映射为 y”;点与点之间的映射是一条直线,低于第一个点的 x 值时取该点的 y 值(高于最后一个点时同理),因此曲线不必覆盖整个范围。filters:curve(rgb,0,20,128,160,255,255) 会同时提亮所有通道的阴影和中间调;再添加一个使用 r、g 或 b 的 curve,即可逐个调整通道。恒等曲线 curve(rgb,0,0,255,255) 不会产生任何变化。

convolution 滤镜可实现内置滤镜无法提供的锐化、模糊和边缘效果:卷积核由你自行指定。数值按行优先顺序书写(第一个值是窗口左上角的采样值,最后一个值是右下角的采样值),每个值都是普通小数,例如 0.0625(不支持指数形式),范围为 −1024 至 1024。末尾的标志用于开启或关闭归一化:设为 1 时,应用前会将卷积核按其元素之和缩放,因此全 1 卷积核 convolution(1,1,1,1,1,1,1,1,1,1) 会执行盒式平均;当元素之和为 0(通常是边缘卷积核)时,无论标志为何,都会按原样应用卷积核。落在帧外的采样点会读取最近的边缘像素,规则与 blur 和 sharpen 相同。filters:convolution(0.0625,0.125,0.0625,0.125,0.25,0.125,0.0625,0.125,0.0625,0) 是一个 3×3 高斯模糊。

curl "http://localhost:3000/unsafe/600x400/filters:sharpen(1.2):saturation(1.1)/https://example.com/photo.jpg"

编码指令不会更改像素,只会影响编码器的行为:

指令参数描述示例
formatformat(webp)、jpeg(或 jpg)、png、webp、jxl、avif、gif、mp4、webm输出格式。优先于 Accept 协商。视频格式只能显式指定(不会协商),且需要动画源和已配置的编码器;参见视频输出。filters:format(avif)
qualityquality(80),1 至 100,或 quality(auto)JPEG、JPEG XL、AVIF 和 WebP 的编码质量。默认值为 80。filters:quality(auto)
max_bytesmax_bytes(50000),字节数预算,至少为 1逐步降低质量并重新编码,直到输出符合预算。filters:max_bytes(50000)
fillfill(ff0000),六位十六进制数字,RGB编码为 JPEG 时用于展平透明度的背景颜色。filters:fill(ffffff)
no_upscale无将请求中的每次调整尺寸限制在源图像尺寸以内。filters:no_upscale
dprdpr(2),取值为 1、1.5、2、3 之一按设备像素比缩放尺寸和裁剪目标,使同一个 URL 可用于标准显示屏和 Retina 显示屏。filters:dpr(2)

关于这些指令的说明:

  • quality(auto) 会根据内容分析为每张图像选择质量,范围为 50(适用于平坦图像)到 95(适用于细节丰富的图像)。对于 JPEG 和 JPEG XL,所选质量随后会经过验证:crabcrop 会编码、解码结果,并使用 SSIMULACRA2 将其与处理后的源图像进行评分,最多调整质量并重新编码 4 次,直到分数达到目标值。目标值默认为 75,可通过 CRABCROP_SSIM_TARGET 配置。达到的分数会在元数据响应中以 ssimulacra2 报告。验证仅在缓存未命中时进行,但仍需要实际计算:为一张全尺寸图像评分大约需要 1 秒。由 crabcrop 选择质量(适用于 quality(auto)),或由 max_bytes 调低质量时,响应会携带 X-Crabcrop-Quality 标头,其值为实际使用的质量。
  • max_bytes 只能控制带有质量设置的格式:JPEG、JPEG XL、AVIF 和 WebP。如果质量降至下限后输出仍超出预算,则会返回最小体积的编码结果,而不是报错。PNG 和 GIF 会忽略该预算。
  • WebP 会按请求的质量进行有损编码(默认值为 80);quality(100) 会选择无损编码。对于 PNG 或 GIF 输出,quality 不起作用。
  • JPEG 没有 alpha 通道。使用 fill 时,透明像素会与填充颜色合成;否则会直接使用存储的 RGB 值。
  • no_upscale 在请求中出现的位置不影响其作用范围:对于 200×150 的源图像,300x200 的调整大小请求会返回按比例缩放后的 200×150 图像。
  • dpr 同样作用于整个请求,并会在 no_upscale 限制生效前缩放目标尺寸,因此两者同时设置时,dpr 不会生成超出源图像的像素。目标尺寸会取整到整数像素。crop-rect 和 focal 坐标指向源图像像素,不会缩放。如果 URL 中没有 dpr,服务端会遵循 DPR 客户端提示标头(取值范围相同;无效提示会被忽略)。URL 中明确设置的 dpr 始终优先于标头。
客户端提示

如果 URL 本身没有指定尺寸(没有尺寸、裁剪、适应尺寸、填充或 crop-rect 操作),服务端会遵循宽度客户端提示,因此响应式页面无需针对每种宽度生成 URL。服务端会依次读取 Sec-CH-Width、Width、Sec-CH-Viewport-Width 和 Viewport-Width,并将图像调整为该宽度,效果相当于 URL 中带有仅指定宽度的尺寸操作(DPR 提示仍会在此基础上进行缩放)。提示宽度会向上量化为 CRABCROP_WIDTH_STEP 的整数倍(默认值为 100),使相近的视口共用一个缓存条目;量化后的宽度也会加入缓存键。图像响应会为这些提示声明 Accept-CH,并根据这些提示进行区分。

带有 Save-Data: on 的请求会将带有质量设置的格式(JPEG、JPEG XL、AVIF、WebP)的默认编码质量下调一档,从 80 降至 70;下调后的值会通过 X-Crabcrop-Quality 报告。自行设置 quality(...) 的 URL 不受影响,下调后的响应会单独缓存。

curl "http://localhost:3000/unsafe/1200x0/filters:format(webp):quality(auto)/https://example.com/photo.jpg"

水印会叠加另一张图像:

filters:watermark(<url>,<x>,<y>,<alpha>)
参数说明
url水印图像的 URL,需进行百分号编码(它位于路径段中,因此至少要对 : 和 / 进行编码)。URL 本身可以包含逗号。
x、y水印左上角的位置,单位为像素。负值表示从右侧和底部边缘定位。
alpha不透明度,范围为 0(不可见)到 100(不透明)。水印自身的 alpha 值会得到保留。

服务端会使用与源图像相同的加载器、限制和源策略获取每张水印图像。水印加载或解码失败时,请求也会失败(502),而不是跳过水印。

# Overlay https://cdn.example.com/logo.png at the bottom right, 80% opaque
curl "http://localhost:3000/unsafe/filters:watermark(https%3A%2F%2Fcdn.example.com%2Flogo.png,-10,-10,80)/https://example.com/photo.jpg"

叠加通过混合模式和相对尺寸扩展了水印功能:

filters:overlay(<url>,<x>,<y>,<alpha>,<blend>)
filters:overlay(<url>,<x>,<y>,<alpha>,<blend>,<width>)
参数描述
url, x, y, alpha与 watermark 相同:经过百分号编码的 URL(可包含逗号)、位置(负值表示以右侧和底部边缘为锚点),不透明度为 0 到 100。
blend叠加层的颜色与下方图像的混合方式:normal(水印的行为)、multiply、screen 或 overlay,均为逐通道 CSS 公式。混合时使用有效 alpha 值(叠加层自身的 alpha 值乘以 alpha),并保留底图的 alpha 通道。
width可选。合成前,将叠加层缩放至底图宽度的指定百分比(1 到 400),并保持其宽高比。

叠加图像的获取方式以及预取、加载和失败语义均与水印相同:叠加层加载失败会导致请求失败。

curl "http://localhost:3000/unsafe/filters:overlay(https%3A%2F%2Fcdn.example.com%2Ftexture.png,0,0,60,multiply,100)/https://example.com/photo.jpg"

文本叠加会使用服务器上配置的字体绘制标签:

filters:text(<text>,<size>,<color>,<anchor>)
参数描述
text经过百分号编码的标签。编码后可包含逗号和 Unicode 字符。
size字体大小,单位为像素,范围为 1 到 1000。
colorrrggbb,或包含 alpha 值的 rrggbbaa。
anchor九个网格点之一:top-left、top-center、top-right、center-left、center、center-right、bottom-left、bottom-center、bottom-right。标签距边缘的内边距为字体大小的一半。

文本会在过滤器运行时绘制,使用图像在处理流程中该位置的分辨率,因此请将 text 放在缩放操作之后,以保持清晰。字体取自服务器上的 CRABCROP_FONT_PATH;如果未配置字体,text 请求会失败并返回 501。

curl "http://localhost:3000/unsafe/800x600/filters:text(Sale%20ends%20soon,32,ffffff,bottom-right)/https://example.com/photo.jpg"

AI 过滤器需要在服务器上配置模型。请参阅 AI 功能。

过滤器参数描述示例
removebg无移除背景,让主体保留在透明背景上。输出格式必须支持 alpha:png、webp、jxl 或 avif。filters:removebg:format(png)
studiostudio(product,...),命名参数:bg、shadow、pad工作室模式:将主体合成到背景上,并添加合成的接触阴影,再缩放主体并居中,使其与边距保持指定距离。请参阅 工作室模式。需要 removebg 模型。filters:studio(product,bg:white)
keep_profile无保留源 ICC 配置文件,而不是转换为 sRGB;配置文件会嵌入 JPEG 和 PNG 输出中。其他输出格式会转换为 sRGB(如果像素未标记为任何配置文件,颜色就会不正确)。filters:keep_profile
blurfaces无模糊所有检测到的人脸(隐私遮蔽)。需要人脸模型。filters:blurfaces
pixelatefaces无将所有检测到的人脸像素化(隐私遮蔽)。需要人脸模型。filters:pixelatefaces
blurclassblurclass(person)模糊所有检测到的该类别对象(隐私遮蔽)。可重复使用以处理更多类别。需要检测模型。filters:blurclass(person)
pixelateclasspixelateclass(car)将所有检测到的该类别对象像素化(隐私遮蔽)。可重复使用以处理更多类别。需要检测模型。filters:pixelateclass(car)
facepadfacepad(0.5),0 到 3用于 crop:...:face 的内边距:在裁剪窗口的每一侧增加指定比例的人脸尺寸。默认值为 0.5。crop:200x200:face/filters:facepad(1)
upscaleupscale(2) 或 upscale(4)按指定倍数进行 AI 超分辨率处理。每个维度的输出上限为 4096 px。filters:upscale(2)
curl "http://localhost:3000/unsafe/filters:removebg:format(png)/https://example.com/product.jpg"
工作室模式

studio(product,...) 描述的是结果,而非处理机制:一张可直接用于商品列表的产品图。一个滤镜会展开为固定的处理流水线,并在链条中该滤镜所在的位置运行:

  1. 抠图:使用与 removebg 相同的分割模型抠出主体(如果未配置分割模型,studio 会像 removebg 一样失败并返回 501)。
  2. 去边缘杂色:根据局部背景去除边缘颜色中的杂色,避免旧背景残留光晕。
  3. 背景和阴影:画布(即链条运行到该位置时的图像框)填充为解析后的背景色,并根据主体的 alpha 通道生成接触阴影,经过模糊和加深处理后向下偏移,置于主体下方。
  4. 留白:缩放并居中主体,使每侧都保留留白边距。
# The defaults: brand backdrop (white when none is configured), soft shadow, pad 10
curl "http://localhost:3000/unsafe/filters:studio(product)/https://example.com/shoe.jpg"
# Every argument named
curl "http://localhost:3000/unsafe/filters:studio(product,bg:f5f0e8,shadow:strong,pad:25)/https://example.com/shoe.jpg"
参数值默认值
bgwhite、六位十六进制颜色、brand 或 none已配置时使用品牌色,否则为 white
shadowsoft、strong 或 nonesoft
pad0 到 40,每侧的边距百分比10

注意事项:

  • bg:brand 会填充运营方的品牌色,通过 CRABCROP_BRAND_COLOR 设置。未配置时会解析为白色。bg:none 会保留主体的透明度,并跳过阴影处理。
  • 如果未检测到主体,回退逻辑会将原图居中并留白后放在解析出的背景上(不添加阴影),而不是返回空画布。
  • 结果是链条中的普通图像,可用于所有输出格式(包括 JPEG):与 removebg 不同,背景填充后不会保留透明区域。后续操作会叠加处理,因此 filters:studio(product)/1200x1200 会调整合成图的尺寸,而 studio 之后的 pad: 操作会像处理任何图像一样为合成图留白。编码时的 fill 指令仅在仍有透明区域时才会生效;在 studio 之后,这意味着需要使用 bg:none 的处理链。
  • Meta 响应会在 studio 字段中报告本次运行的信息:解析后的背景及其来源(argument、brand 或 default)、应用的阴影和留白、运行的步骤、采用的回退处理(品牌色解析、未检测到主体时的回退),以及提供方提供的遮罩置信度。请参阅Meta 响应。

预设

预设为服务器上的操作字符串命名,因此 URL 保持简短,转换菜单也由操作员控制。在文件中定义预设,每行一个:

# /etc/crabcrop/presets.txt
og-card = crop:1200x630:smart/filters:format(webp):quality(80)
thumb = 300x200

将 CRABCROP_PRESETS_FILE 指向该文件后,请求即可在适用操作的位置使用 preset:<name>:

curl "http://localhost:3000/<signature>/preset:og-card/https://example.com/photo.jpg"

签名涵盖预设引用;服务器会在验证后确定性地展开预设,缓存条目则以展开后的操作为键。请将预设视为稳定模板:绝不要原地修改定义。如需更改,请使用新名称(thumb2)发布,并更新模板以引用它。原地修改会改变已签名并分享的 URL 背后的字节;图片响应会附带 Cache-Control: immutable,有效期为一年,因此浏览器和 CDN 持有的旧响应会继续提供服务,无法通过清除缓存解决。(问题不在此服务器自身的缓存:它们以展开后的形式为键,因此会采用修改后的定义。无法更新的是下游副本。)清除缓存时可以直接提供预设 URL,即操作员持有的 URL;服务器会先展开它,再匹配条目,而 /purge-group 可一次清除某个预设的全部输出。未知的预设名称会返回 400。预设定义会在启动时验证:格式错误的条目会导致启动失败,预设也不能包含源片段、exp: 或其他预设。

设置 CRABCROP_ONLY_PRESETS=1 后,自由格式操作会被拒绝并返回 400:请求只能使用预设引用、exp: 和 maxage: 元数据,以及裸源。此模式不会改变签名规则。结合 CRABCROP_ALLOW_UNSAFE=1,即可实现公共部署:浏览器和 CMS 可以仅通过预设名称构建 URL,无需保管密钥,也无法自行发明转换。

客户端库

URL 语法和签名方案在 spec/url-signing.md 中指定;spec/test-vectors.json 中包含一组规范测试向量,每个实现都会将其作为测试套件运行。基于该规范构建的库:

  • Rust:本工作区中的 crabcrop-url crate(crates/crabcrop-url),提供 sign、verify、build_url、build_srcset 以及清除辅助函数 purge_signature 和 purge_group_signature。服务端使用同一个 crate 进行验证,因此客户端和服务端不会出现偏差。
  • TypeScript:sdks/typescript,@crabcrop/url,提供 sign、verifyUrlSignature、buildURL、buildSrcSet、purgeSignature 和 purgeGroupSignature,以及类型化操作构建器(ops、filter),可根据语法进行验证并编译为相同的规范字符串。仅使用标准库;测试通过 node --test 运行。
  • Python:sdks/python,crabcrop_url,提供 sign、verify、build_url、build_srcset、purge_signature 和 purge_group_signature。仅使用标准库;测试通过 python3 -m unittest 运行。

这三种库都接受可选的过期时间(以开头的 exp: 段输出),以及用于 unsafe 形式的 null 密钥,供不安全部署和仅使用预设的部署使用。

frameworks/ 中的框架集成基于 TypeScript 构建器:用于 next/image 的 Next.js 自定义加载器、用于 @nuxt/image 的 Nuxt provider,以及用于外部(非 Shopify CDN)图像的 Hydrogen 加载器。这三者都建议将预设作为配置,因此客户端构建 URL 时无需使用密钥。

演练场是一个静态页面(无需构建步骤,也不保留服务端状态),用于在浏览器中构建、签名、预览和调试 URL:密钥不会离开页面,签名会在本地验证,粘贴的 URL 会按段解析,并突出显示错误。

对于编码 Agent,sdks/mcp 是一个第一方 MCP 服务(stdio,无依赖项),通过相同的 SDK 和 HTTP 接口提供 sign_url、explain_url、fetch_meta 和 purge_result,并通过 CRABCROP_BASE / CRABCROP_SECRET 配置。其 README 中提供了客户端设置片段。

格式协商

如果 URL 未指定 format(...),crabcrop 会根据请求的 Accept 标头选择输出格式。在可协商的格式(AVIF、JPEG XL、WebP)中,q 值最高的格式胜出;未提供 q 值时按 1 计算。q 值相同时,按服务端偏好顺序选择:

  1. image/avif → AVIF
  2. image/jxl → JPEG XL
  3. image/webp → WebP

因此,Accept: image/avif;q=0.5, image/webp 会生成 WebP:客户端的偏好更强,优先于服务端偏好。如果三种格式都未提供,则保留源图像的格式(如果无法检测格式,则使用 JPEG)。被 q=0 拒绝的格式绝不会被选中,*/* 等通配符也不算作可选格式。URL 中显式指定的 format(...) 始终优先于协商结果。动画 GIF 和 WebP 源图像是唯一的例外:它们绝不会协商为 JPEG XL,因为这会丢失动画帧;GIF 源图像会保留 GIF 格式,WebP 源图像会保留 WebP 格式。视频是另一项例外:mp4 和 webm 绝不会参与协商,只能通过显式的 format(...) 指定(参见视频输出)。响应会包含涵盖 Accept 和客户端提示的 Vary 标头,确保下游缓存正确区分缓存键(这些提示同样会改变尺寸和质量;参见客户端提示)。

视频输出

动画 GIF 或 WebP 也可以作为真正的视频提供:H.264 MP4 或 VP9 WebM,即 gifv 模式,可在任何地方内嵌播放,体积仅为 GIF 的一小部分。使用显式格式筛选器发起请求:

/<signature>/filters:format(mp4)/https://example.com/animation.gif

Crabcrop 不自带编解码器。编码由你配置的外部程序执行,即 CRABCROP_VIDEO_ENCODER:服务器上编码器可执行文件的路径,FFmpeg 是参考实现。该路径会在启动时验证(必须存在且可执行,否则服务器拒绝启动)。未设置此变量时,视频功能处于关闭状态,视频 URL 返回 501,与未配置模型的 AI 筛选器完全一样。并且不会静默回退:编码失败会返回错误响应,绝不会返回 GIF。

编码器约定

Crabcrop 会直接运行该程序,不经过 shell,并使用自行构造的 argv;只有程序路径由你指定。帧通过 stdin 以原始 RGBA 格式输入,视频则通过 stdout 输出:

<encoder> -nostdin -v error -f rawvideo -pix_fmt rgba -s <W>x<H> -r <fps> -i pipe:0 -an -vf format=yuv420p <codec args> pipe:1

MP4 的编解码器参数为 -c:v libx264 -preset veryfast -crf 23 -movflags frag_keyframe+empty_moov+default_base_moof -f mp4。分片 MP4 标志是让 MP4 能够写入管道的关键;普通 MP4 需要可寻址文件才能完成索引。WebM 的参数为 -c:v libvpx-vp9 -b:v 0 -crf 30 -f webm。将参数传递给真正编码器的包装脚本(例如以 exec ffmpeg "$@" 结尾的脚本)符合此约定,这也是使用自定义编码器构建版本的方式。由于编解码器位于 crabcrop 之外,编解码器许可问题也由外部承担:H.264 专利池等相关问题由运营方负责,这正是这种分离方式的用意。输出字节会因编码器构建版本而异,因此不保证不同部署之间逐字节可复现;URL 语法和错误则有保证。

编码器配置必须符合以下细节:

  • 帧会合成到黑色背景上,并通过复制边缘像素填充至偶数尺寸(H.264 和 VP9 有此要求)。响应会说明填充后的尺寸。
  • 帧时序:管道约定使用固定帧率,因此 crabcrop 会根据最快的帧确定帧率:取 ceil(1000 / shortest delay),并限制在 1-100 fps(如果没有帧带有延迟,则为 10 fps);每帧重复 round(delay × fps / 1000) 次,至少一次。总时长的误差不超过每个源帧一个帧周期。源动画总延迟超过 600 s 时,将被拒绝并返回 400。
  • 同时最多运行两个编码任务,每个任务都有硬性墙钟超时(CRABCROP_VIDEO_TIMEOUT_SECS,默认 30 s),超时后会终止编码器;还有硬性输出上限(CRABCROP_VIDEO_MAX_MB,默认 64 MB),一旦超过便立即中止编码。编码器运行时使用空环境,不会继承服务器的任何环境变量。
错误
情况响应
未配置编码器501:video output is not configured on this server
静态源(静态图片,或只有一帧的 GIF 或 WebP)400:源是静态图片,视频需要动画源
动画超过 600 s 上限400
编码器以非零状态退出、未产生输出、超过上限或超时502:video encoding failed(原因,包括编码器 stderr 摘录,记录在服务器日志中)
编码队列已满(两个任务都在等待 10 s 后仍处于忙碌状态)503

视频结果与其他响应一样会缓存,使用视频内容类型(video/mp4 或 video/webm);URL 中的每项操作(调整大小、裁剪、滤镜)都会逐帧应用,然后再进行编码。对视频 URL 发起元数据请求(Accept: application/json)会返回 400:元数据描述的是图片,而视频结果不是图片。

元数据响应

对任意图片 URL 发送 Accept: application/json,即可获取处理后图片的元数据,而不是图片字节:

curl -H "Accept: application/json" "http://localhost:3000/unsafe/300x200/https://example.com/photo.jpg"
{"width":300,"height":200,"format":"jpeg","bytes":18234,"source_width":1200,"source_height":800}
字段描述
width, height输出尺寸,单位为像素。
format输出格式:jpeg、png、webp、jxl、avif 或 gif。
bytes处理后图像的大小,单位为字节。
source_width, source_height应用 EXIF 方向后,源图像的尺寸,单位为像素。
ssimulacra2输出的经验证感知评分;当 quality(auto) 验证 JPEG 或 JPEG XL 编码时提供。100 表示完全相同。
studio当运行了 studio(...) 筛选器时提供:解析后的背景(bg,格式为 #rrggbb,透明时为 none)及其来源(bg_from:argument、brand 或 default)、应用的 shadow 和 pad、运行的扩展 steps、采用的 fallbacks,以及掩膜提供方提供时的 mask_confidence。请参阅工作室模式。

元数据响应通过 Cache-Control: private, no-store 提供:一个 URL 有两种表示形式(图像和 JSON),中间层不得将其中一种固定为另一种。

将 placeholders 参数添加到媒体类型,即可获取占位符数据;这些数据根据最终处理后的像素计算得出。该参数在不带值(; placeholders)、设为 1 或设为 true(不区分大小写)时启用;任何其他值(包括 0 和 false)均不启用。下方的 detections 参数遵循相同规则。在命令行中,crabcrop meta --placeholders 和 crabcrop meta --detections 会为你设置这些参数:

curl -H "Accept: application/json; placeholders=1" "http://localhost:3000/unsafe/300x200/https://example.com/photo.jpg"

这会添加四个字段:dominant_color,图像的平均颜色,格式为 #rrggbb;placeholder,宽 16 px 的模糊 JPEG 预览,格式为 base64 数据 URI;palette,最显眼的五种颜色,格式为 #rrggbb,按显眼程度从高到低排列;以及 thumbhash,一种 ThumbHash 字符串,ThumbHash 客户端库可将其解码为宽高比正确的模糊预览。适合在真实图像加载时显示预览。配置了 CLIP 模型时(见 AI 功能),meta 响应还会携带 tags(带有零样本评分的标签)和由最佳标签组成的 alt_text,无论是否请求了占位图。配置了检测模型时,在同一个 Accept 条目中添加 detections=1 会增加 detections:处理后的图像中检测到的对象,格式为像素坐标 {label, score, x, y, w, h, detector},每个检测器的结果按最佳项优先排列;detector 表示生成边界框的提供方(builtin,或已声明的检测器名称,见已声明的检测器)。配置了审核功能时(见 AI 功能),在同一个 Accept 条目中添加 moderation=1 会增加 moderation:配置模型按类别给出的判定分数,格式为 {model, scores: [{label, score}]};如果只配置了标签文件,则会返回由 CLIP 评分的零样本 signals,并标记为 review_only,因为信号只是审核线索,绝不是判定结果。如果无法响应选择加入的请求(未配置、推理门控繁忙或运行失败),该字段会报告 {"available": false, "reason": ...},且 meta 请求仍会成功。判定结果只会附加到 meta 中:分数绝不会改变图像 URL 所提供的内容。配置了 CLIP 模型时,在同一个 Accept 条目中添加 embeddings=1 会增加标注器的图像嵌入:embedding 是以 little-endian float16 打包并经过 base64 编码的向量,同时还会提供 embedding_model、embedding_dim、embedding_encoding(f16le-base64)和 embedding_normalized: true,以便更换编码器后,存储的向量仍可解读。无法响应这项选择加入请求时,embedding 为 null,并附带 embedding_reason,且 meta 请求仍会成功。设置了 CRABCROP_AESTHETIC_HEAD 时(见 AI 功能),meta 响应还会携带 aesthetic:该头对同一嵌入的评分,格式为 {head, model, score};如果头与编码器的维度不一致(此时字段会列出 expected 和 got),或者无法生成向量,则分数为 null,并附带原因。美学评分仅供描述使用:它绝不会限制、排序或改变 URL 所提供的内容。Meta 请求从不缓存。唯一的拒绝情况:对视频 URL 发起 meta 请求会返回 400,因为视频结果不是图像(见视频输出)。Meta 模式下的错误会保留通常的状态码;语法错误会返回错误中所述的 JSON 错误封装。

响应

成功的图像响应包含:

标头值
Content-Type输出格式的媒体类型,例如 image/webp。
Cache-Controlpublic, max-age=31536000, immutable
VaryAccept, DPR, Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width, Width, Viewport-Width, Save-Data
X-Crabcrop-Quality仅当 crabcrop 选择了质量(quality(auto)、max_bytes 调整或 Save-Data 降级)时出现;值为所用的质量。
X-Crabcrop-Fallback当响应使用配置的备用图像而非源图像时为 1(见备用图像)。
缓存

签名 URL 采用内容寻址,因此成功响应会被缓存并直接提供,无需重新获取或重新处理。条目以请求的规范形式(解析后的操作采用其唯一拼写)为键,因此同一请求的等效拼写会共用一个条目。第一层是按字节数限制的内存 LRU:默认 256 MB,可通过 CRABCROP_CACHE_MAX_MB 配置(0 表示禁用)。设置 CRABCROP_RESULT_STORAGE_DIR 后,下方会增加第二层磁盘存储(由 CRABCROP_RESULT_STORAGE_MAX_MB 限制,默认 512 MB):结果在重启后仍会保留,磁盘命中项会提升到内存层。设置 CRABCROP_RESULT_BUCKET 后,本地层后方会增加第三层:所有副本共享一个 S3 存储桶(或通过 CRABCROP_S3_ENDPOINT 和 CRABCROP_S3_REGION 指定的 S3 兼容端点,并使用当前 AWS 凭据签名)。共享层命中项会提升到磁盘和内存层;新结果会在后台写入存储桶;清除操作会传播到全部三层。CRABCROP_RESULT_PREFIX 用于为存储桶中的对象设置命名空间。共享条目采用磁盘层格式存储,该层按设计尽力而为:存储桶出错时按未命中处理,而不会导致请求失败。只有 200 响应会被缓存;错误响应不会缓存。由于响应可能取决于请求标头,协商响应会按协商格式类别、有效 DPR 值、量化后的宽度提示类别以及 Save-Data 类别分别缓存。

对于同一未缓存 URL 的并发请求,它们会共用一次获取和一次编码:第一个请求者执行操作,其余请求等待,之后提供已存储的条目。缓存键还包含影响输出的配置指纹(元数据策略、质量调优参数、宽度提示步长,以及正在使用的模型、字体、编码器和检测器文件),启动时会打印为 configuration fingerprint: ...。如果重启时其中任一项发生变化,就会开始一个新的缓存周期:持久层会将旧条目保留在旧键下,使其无法访问,直至条目过期,而不会提供基于先前配置生成的渲染结果。预设和默认操作无需指纹:它们本身就会改变构建每个键所依据的规范形式。

持久层中的条目均经过身份验证:每个已存储条目都带有使用服务器密钥派生出的密钥生成的 HMAC-SHA256 标签,并会在提供条目或启动时建立索引前进行验证。由不掌握密钥的人放入缓存目录或存储桶的文件或对象,以及磁盘上被改动过一个字节的已存储条目,都会按未命中处理并重新渲染,绝不会被提供。因此,轮换密钥也会使持久层内容失效:使用旧密钥写入的条目将无法通过验证,并会在需要时替换。(内存层仅限当前进程使用,无需标签。)

色彩管理和元数据

像素会在源图像配置文件对应的色彩空间中解码。如果源图像嵌入了 ICC 配置文件(读取 JPEG 和 PNG 源图像时会读取该配置文件),crabcrop 会在运行任何操作前,通过嵌入式配置文件色彩变换将图像转换为 sRGB,因此尺寸、裁剪和滤镜都在同一已知色彩空间中运行;没有配置文件的源图像则默认已是 sRGB。filters:keep_profile 可按请求选择不进行转换,并将源配置文件嵌入 JPEG 和 PNG 输出中。CMYK JPEG 会通过 JPEG 解码器自身的 CMYK 转换进行解码(目前尚不支持嵌入式 CMYK 配置文件);灰度配置文件不会进行转换。

元数据遵循由运维人员设置的策略 CRABCROP_METADATA:

值行为
strip(默认)输出不包含源元数据。
preserveJPEG 输出会附带源图像的 EXIF、XMP 和 Photoshop(IPTC)分段;PNG 输出会附带 EXIF 和 XMP。其他输出格式不保留元数据。

保留元数据时会直接复制源分段,而非重新序列化;只有一项修补:EXIF 方向会被改写为 1,因为像素在解码时已经旋转,原始值会导致查看器再次旋转。无论采用哪种策略,解码时始终会遵循 EXIF 方向。

备用图像

设置 CRABCROP_FALLBACK_IMAGE(URL 或启动时读取的本地文件路径)后,无法获取或解码的源图像会降级为备用图像,而不是返回错误:备用图像会经过相同的操作处理,并以状态码 200 和标头 X-Crabcrop-Fallback: 1 返回。签名和解析失败绝不会触发回退,403 或 400 仍会如实返回,源策略拒绝也不会触发回退。备用响应绝不会被缓存,因此源站恢复后,下一个请求即可获取恢复后的图像;这些响应还会附带 Cache-Control: no-store,确保 CDN 或浏览器不会一直用占位图替代真实图像。未配置备用图像时,错误行为保持不变。

错误

错误响应正文为纯文本,包含简短消息。发送 Accept: application/json 后,语法错误(下文的 400)会改为返回 v2 JSON 封装:{"error":{"code":"invalid_filters","message":"invalid filters segment: filters:bogus(1)"}}。错误代码是解析器稳定的名称,发布后即固定:empty_path、missing_signature、malformed_signature、invalid_size、invalid_crop、invalid_crop_rect、invalid_pad、invalid_expiry、unknown_preset、invalid_preset、presets_only、invalid_filters、missing_source_url、invalid_focal_rect、invalid_maxage、invalid_debug、invalid_curve、invalid_convolution、invalid_noise。可能收到的状态码如下:

状态码含义
400路径或操作无法解析,或不支持该操作:格式错误的片段、未知滤镜或参数、将 removebg 用于不支持 alpha 通道的格式、upscale 超过 4096 px 上限、crop-rect 超出图像范围,或对静态源请求视频格式。
403签名错误、exp: URL 已过期,或源 URL 被源策略拒绝(主机不在 CRABCROP_ALLOWED_SOURCES 中,或启用 CRABCROP_BLOCK_PRIVATE_SOURCES 时解析到非公开地址)。
404未配置上传时,对 /upload/... 执行 PUT;或对任何非上传路由的路径执行 PUT。
413上传正文超过 CRABCROP_UPLOAD_MAX_MB 上限。
500输出编码失败,或 AI 模型在推理时失败(运行时出错,或模型会话锁因先前的故障而中毒)。完全无法加载的模型会导致启动失败,而不会响应请求。
501URL 有效,但此服务未针对该 URL 进行配置:未配置模型的 AI 滤镜、未配置字体的文本叠加,或未配置编码器的视频格式。
502无法获取源内容(网络错误、源站返回非 2xx 响应,或 Content-Type 不是图像类型);源内容超过 10 MiB 获取上限;或获取的字节无法解码为图像,包括未启用 AVIF 解码的构建版本中的 AVIF 源、损坏的 HEIC 源以及无法解码的格式。水印加载失败也会返回此状态;配置的 AI 模型运行后产生了服务端无法使用的输出(没有输出、张量无法读取或形状异常),也会返回此状态。配置的视频编码器发生故障、超时或超出输出上限时,同样会返回此状态。
503请求因处理、AI 或视频门控等待超过超时时间而被服务端拒绝;响应中会包含 Retry-After。
504获取源内容超时(10 秒)。

例如,向未配置背景移除模型的服务发送 removebg 请求,会返回 501,正文为 removebg is not configured on this server。

端点

路由描述
GET /health健康检查。返回 200,正文为 ok。
GET /metricsPrometheus 文本格式的指标:按状态类别统计请求数(crabcrop_requests_total)、请求/加载/处理耗时、缓存命中与未命中计数、加载器与引擎错误计数以及字节总数。
POST /purge/<signature>/<path>清除一个结果在所有缓存层中存储的所有变体。见下文。
POST /purge-group/<signature>/<group>清除一个缓存组在所有缓存层中存储的所有结果。见下文。
PUT /upload/<signature>/<name>将一个原始文件存储到已配置的上传目标。仅在配置了上传时存在。参见上传原始文件。
GET /explain/<signature>/...返回 URL 的说明报告,而不是图像。参见自解释 URL。
GET /<signature>/...图像请求,如上文所述。
清除缓存结果
curl -X POST "http://localhost:3000/purge/<signature>/300x200/https://example.com/photo.jpg"

<path> 是不含签名的原始请求路径。签名会对按原样提供的目标进行计算;随后,目标会以与条目键相同的方式进行规范化,因此清除路径的任何等效写法(jpg 别名、前导零、预设引用)都会删除共享条目,包括 exp: 变体。请使用客户端库计算签名(Rust crate 和 Python SDK 中的 purge_signature,TypeScript 中的 purgeSignature):它是以从密钥派生的清除子密钥对路径计算的 HMAC-SHA256,因此只有持有密钥的人才能使缓存失效,而且图像签名绝不能重放为清除签名。成功响应为 200,正文类似 purged 2:计数涵盖该条目及其所有变体(按格式类别和 DPR 区分)。清除签名无效时返回 403,且不会删除任何内容;清除未存储的路径时则返回 purged 0。

上传原始文件

crabcrop 按设计仅负责获取:源文件存放在其他位置,转换通过 URL 引用它们。上传端点是唯一有意设置的例外,而且默认关闭,只有操作人员启用后才可用。请准确配置以下一个目标:

  • CRABCROP_UPLOAD_DIR=/srv/originals 会将原始文件作为文件存储在本地目录中。
  • CRABCROP_UPLOAD_BUCKET=originals 会将原始文件存储在 S3 存储桶中,并复用源 S3 配置(端点、区域和环境中的 AWS 凭证);CRABCROP_UPLOAD_PREFIX 用于为对象设置命名空间。如果已配置凭证,该存储桶也必须出现在 CRABCROP_ALLOWED_BUCKETS 中,因为上传的对象会作为 s3:// 源提供服务。crabcrop 不支持 GCS 目标:gs:// 源在 crabcrop 中只能读取。

如果两个变量都未设置,该端点就不存在:PUT 请求会直接返回 404。如果设置了其中一个变量,上传就是一次带签名的 PUT:

curl -X PUT --data-binary @photo.jpg "http://localhost:3000/upload/<signature>/heroes/photo.jpg"

<name> 是对象名称,由以 / 分隔的 [A-Za-z0-9._-] 字符串段组成(不得包含空段、. 或 .. 段,不得以点开头,且最多为 255 字节)。签名会对按原样提供的名称进行计算,使用从密钥派生的上传子密钥;其构造方式与清除签名所用的域分离方式相同:只有持有密钥的人才能上传,图像签名或清除签名绝不能在此处重放,而且 unsafe 绝不能授权上传。请使用客户端库计算签名(Rust crate 和 Python SDK 中的 upload_signature,TypeScript 中的 uploadSignature),也可以让 CLI 完成整个流程:

crabcrop upload ./photo.jpg heroes/photo.jpg

上传令牌可以携带有效期,委托令牌则应该设置有效期:使用 PUT /upload/<signature>/exp:<unix-seconds>/<name> 时,签名会同时涵盖 exp: 段和名称,因此令牌会在指定秒数时失效,且既不能移除有效期,也不能延长有效期。客户端库可通过 expires 参数生成这种形式,CLI 则使用 --expires <unix-seconds>。 请求正文必须能够解码为此服务可提供的图像(使用与转换请求相同的解码路径,包括大小和尺寸上限);其他内容都会返回 415,且绝不会被存储。验证解码与转换共用同一个进程闸门,因此服务器饱和时会以 503 和 Retry-After 拒绝上传,而不是绕过闸门进行解码。CRABCROP_UPLOAD_MAX_MB 限制单次上传的大小(默认 10 MB,与源文件获取上限相同,因此存储的原始文件始终能被加载器重新获取;启动时会拒绝更大的配置)。存储的是经过验证的内容:对于 SVG,存储的是经过净化的文档(在存储时剥除脚本和事件处理器,而不仅仅是在 crabcrop 将其提供回来时剥除);文件名的扩展名必须与正文通过验证的媒体类型相符,因此 PNG 不能存储为 photo.jpg 或 promo.html(没有扩展名的名称则按原样接受)。上传到已存在的名称时会返回 409,并在解码正文前响应;最终写入采用条件创建,因此即使有其他上传同时竞争同一个新名称,这项保证依然成立;设置 CRABCROP_UPLOAD_ALLOW_OVERWRITE=1 可允许 PUT 替换现有文件(返回 200,而不是新对象对应的 201)。替换文件的 PUT 还会清除被替换原始文件在所有层级中的每个缓存衍生版本,因此下次转换请求会根据新字节重新渲染,而不是提供过期结果。此功能不提供列表和删除操作。

响应正文是可嵌入转换 URL 的确切源引用(本地目标使用 file:///heroes/photo.jpg,存储桶使用 s3://originals/heroes/photo.jpg),也会通过 X-Crabcrop-Source 标头返回。文件通过常规加载器提供:未配置 CRABCROP_FILE_ROOT 时,本地目标会同时作为 file:// 源根目录(若已配置该变量,则目标必须位于其指定目录下),因此 PUT 返回后,上传的原始文件即可用于转换。

有一点安全注意事项需要明确说明:启用上传后,此服务就从仅获取内容变为会接收并存储调用方提供字节的服务。上述缓解措施包括:默认关闭、使用独立且带有效期表单的签名域、先解码再存储、存储经净化的 SVG、绑定扩展名、限制大小、默认不允许覆盖,以及不提供列表或删除操作。目标位置仍应是专用于上传的目录或存储桶,并像其他存放用户提供原始文件的位置一样受到监控。如果直接提供目标位置中的内容,请按图像提供:存储的字节是名称受限且已验证的图像,而非任意内容;专用主机还能让任何假想的类型混淆都不会影响主域名。

可自解释的 URL

每个 crabcrop URL 都能自我说明。取任意图片 URL,在签名段前加上 explain/,服务器就会返回该 URL 的报告,而不是图片:

curl "http://localhost:3000/explain/<signature>/crop:1200x630:smart/filters:format(avif):quality(auto)/https://example.com/photo.jpg"

浏览器会显示易读的 HTML 页面;发送 Accept: application/json 可获取相同报告的 JSON 格式。报告包含签名判定(是否有效、使用了哪个密钥、链接何时过期)、来源及其探测到的信息(媒体类型、像素尺寸、编码后大小)、按顺序以通俗语言说明的每项操作、引用的预设及其展开内容、预测结果(当处理链能确定输出尺寸时给出输出尺寸、输出格式、输出像素数与总预算的对比)、缓存时使用的规范形式及其缓存组,以及指向图片本身和调试焦点余数的链接(当处理链包含裁剪时)。CLI 的 explain 动词可离线完成其中一部分(根据你传入的密钥检查语法和签名);服务器路由会补充只有服务器才能获知的实时信息。

将 Explain 暴露给同样持有签名 URL 的调用方是安全的:它会像图片路径一样验证签名和过期时间,并且在访问来源之前完成验证,因此无效或过期的 URL 会收到图片路径自身的响应,且不会产生任何来源抓取;有效 URL 只需通过加载器进行一次有界抓取,所获信息仅来自响应头,不会进行解码或检测,也不会写入任何缓存层。报告只包含可从 URL 和来源本身推导出的信息:不包含机密或密钥材料、配置值、路径或指纹,也不包含重定向或地址详情;错误使用常见的固定代码和通用消息。

缓存头和缓存组

图片响应会携带两个标准标头。Cache-Status(RFC 9211)报告服务器自身缓存的处理结果:内存命中时为 crabcrop; hit,磁盘命中时为 crabcrop; hit; detail="disk",共享层命中时为 crabcrop; hit; detail="shared",响应由源站生成时为 crabcrop; fwd=uri-miss。

对于 URL 引用的每个预设,响应也会归属相应的缓存组(RFC 9875),并列在 Cache-Group 标头中:

Cache-Group: "small"

缓存组让预设范围的失效操作只需调用一次,无需编写脚本遍历每个 URL。组成员会逐渐累积:某个条目即使在任何组被引用之前就已存储,也会在首次通过该 URL 的任意组内写法提供服务时加入相应组,因此清除缓存组时也会涵盖最初通过不含预设的普通 URL 渲染的条目。签名由相同的库生成(purge_group_signature / purgeGroupSignature);这些库会推导清除组端点自身的子密钥,并代你签署组名,因此无需手动构造:

curl -X POST "http://localhost:3000/purge-group/<signature>/small"

响应状态为 200,正文类似 purged 3:清除所有缓存层中该组记录的每个条目。签名无效时返回 403,且不会删除任何内容;未知组则返回 purged 0。

每个图片请求还会向标准输出写入一行结构化 JSON 日志,其中包含操作摘要、状态、各阶段耗时和缓存结果(绝不包含完整的来源 URL)。

命令行

crabcrop 二进制文件也可用作自身的运维 CLI:当第一个参数是动词时,它会执行该动词并退出,而不是启动服务器。配置优先使用标志,其次使用环境变量,规则与 MCP 服务相同:--base / --secret 优先于 CRABCROP_BASE / CRABCROP_SECRET;base 默认值为 http://localhost:3000。

export CRABCROP_BASE=http://localhost:3000
export CRABCROP_SECRET=your-secret

# Sign a URL (validated against the server grammar first)
crabcrop sign "300x200/filters:grayscale/https://example.com/photo.jpg"

# Explain a URL segment by segment, verifying its signature
crabcrop explain "http://localhost:3000/<signature>/300x200/filters:grayscale/https://example.com/photo.jpg"

# Fetch the meta JSON for an image (a bare remainder is signed first)
crabcrop meta "300x200/https://example.com/photo.jpg"

# ... with placeholder fields and detection boxes included
crabcrop meta --placeholders --detections "300x200/https://example.com/photo.jpg"

# Purge one URL, or a whole preset group (Cache-Group)
crabcrop purge "300x200/https://example.com/photo.jpg"
crabcrop purge-group thumb

# Upload an original to the server's upload store (prints the
# source reference for transform URLs; see Upload an original)
crabcrop upload ./photo.jpg heroes/photo.jpg

# Convert a Thumbor URL (re-signed; losses are reported on stderr)
crabcrop convert-thumbor "unsafe/300x200/smart/https://example.com/photo.jpg"

配置

所有配置均来自环境变量。包括布尔值在内,任何无效值都会导致启动错误;服务器会拒绝启动,而不是使用错误的端口、缓存预算或安全标志运行。布尔变量接受 1、true、yes、on(不区分大小写)表示开启,接受 0、false、no、off 表示关闭;其他任何值都会导致启动失败,并指出变量名称。变量名称遵循统一模式:*_MODEL 表示 AI 模型制品,*_FILE 表示启动时读取的文件,*_DIR 表示目录,*_MAX_MB 表示以兆字节为单位的大小预算。

变量默认值说明
CRABCROP_SECRET必需用于验证签名 URL 的 HMAC 密钥,至少 16 字节。未设置时,服务不会启动。
CRABCROP_SECRET_KEY_IDcurrent当前密钥的 ID,由带密钥的签名段(<key-id>:<tag>)标识。
CRABCROP_SECRET_PREVIOUS未设置轮换期间即将弃用的密钥;服务会同时使用它和当前密钥进行验证。长度下限相同。
CRABCROP_SECRET_PREVIOUS_KEY_IDprevious上一个密钥的 ID。必须与当前密钥的 ID 不同。
CRABCROP_DEFAULT_OPS未设置添加到每个请求前的操作(统一格式、质量或水印),启动时会验证。请参阅服务级默认操作。
PORT3000监听端口。CRABCROP_PORT 是带命名空间的别名;两者都设置时,以它为准。
CRABCROP_ALLOW_UNSAFE关闭接受 unsafe URL(布尔值的写法请参阅上文)。
CRABCROP_CACHE_MAX_MB256结果缓存预算,单位为 MB。0 表示禁用缓存。
CRABCROP_SSIM_TARGET75SSIMULACRA2 目标值(0 至 100),quality(auto) 会针对 JPEG 和 JPEG XL 输出验证此值。
CRABCROP_METADATAstrippreserve 会将源 EXIF/XMP(JPEG 还包括 IPTC)复制到 JPEG 和 PNG 输出;请参阅色彩管理与元数据。
CRABCROP_WIDTH_STEP100宽度客户端提示的量化步长,单位为像素:提示的宽度会向上取整至此值的倍数。
CRABCROP_AVIF_SPEED8AVIF 编码的 ravif 速度档位,范围为 1(最慢、输出最小)至 10(最快)。在 800w、质量为 80 时,速度档位 8 冷启动编码约需 1 秒,且在快速档位中生成的文件最小;速度档位 10 大约快 3 倍,但字节数约多 47%。
CRABCROP_JXL_SPEEDfastJPEG XL 编码的 jixel 速度档位:ultrafast、fastest、fast、medium、slow 或 extraslow。较慢的档位会减小输出,但延迟代价很高(在 800w 时,slow 的延迟约为 fast 的 8 倍,字节数约少 18%)。
CRABCROP_PRESETS_FILE未设置预设文件的路径。请参阅预设。
CRABCROP_ONLY_PRESETS关闭拒绝请求中的自由格式操作;只允许预设引用(以及 exp: / maxage: 元数据)。需要至少一个预设。
CRABCROP_ALLOWED_SOURCES未设置(允许所有主机)以逗号分隔的主机后缀,限制加载器可从哪些主机获取内容。后缀匹配主机本身及其任意子域名。
CRABCROP_BLOCK_PRIVATE_SOURCES关闭拒绝主机地址为非公共地址或解析到非公共地址的源:环回、私有、链路本地、未指定、共享(CGNAT)、保留、多播、广播、文档、基准测试及其他特殊用途地址范围,包括这些地址的 IPv4 映射、IPv4 兼容和 6to4 表示形式。连接前以及每次重定向时都会检查。
CRABCROP_S3_ENDPOINT未设置(AWS)用于 s3:// 源的 S3 兼容存储基础 URL,采用路径式寻址。CRABCROP_S3_REGION 用于设置 AWS 区域(默认为 us-east-1)。签名凭据来自 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN;未设置时不签名。
CRABCROP_GCS_ENDPOINThttps://storage.googleapis.com用于 gs:// 源的 GCS XML API 基础 URL。CRABCROP_GCS_REGION(默认为 auto)是签名区域;HMAC 密钥对为 CRABCROP_GCS_ACCESS_KEY / CRABCROP_GCS_SECRET_KEY。
CRABCROP_ALLOWED_BUCKETS未设置以逗号分隔的存储桶名称,限制 s3:// 和 gs:// 方案可读取的存储桶;* 表示允许所有存储桶。配置了存储桶凭据时必须设置(否则服务拒绝启动);未配置凭据时,未设置表示允许所有存储桶,并以未签名方式获取内容。
CRABCROP_FILE_ROOT未设置file:// 源必须位于其下的目录;启动时和每个请求期间都会对路径进行规范化。未设置时禁用文件源。
CRABCROP_REMOVEBG_MODEL未设置用于 removebg 滤镜的 U²-Net 系列 ONNX 模型路径。请参阅 AI 功能。
CRABCROP_BRAND_COLOR未设置studio(...) 合成的品牌颜色,使用 6 位十六进制数字(可带前导 #)。未命名背景和 bg:brand 会解析为此颜色;未设置时为白色。更改此值会开启新的缓存周期,与其他会影响输出的设置相同。请参阅 Studio 模式。
CRABCROP_UPSCALE_MODEL未设置用于 upscale 滤镜的超分辨率 ONNX 模型路径。请参阅 AI 功能。
CRABCROP_FACE_MODEL未设置用于人脸感知 smart 裁剪、crop:...:face 和人脸遮挡滤镜的 YuNet ONNX 模型路径。请参阅 AI 功能。
CRABCROP_CLIP_IMAGE_MODEL / CRABCROP_CLIP_TEXT_MODEL未设置用于 meta tags 和 alt_text 的一对 CLIP ONNX 模型。必须同时设置全部 4 个 CLIP 路径。请参阅 AI 功能。
CRABCROP_CLIP_TOKENIZER未设置CLIP 导出文件的 tokenizer.json。
CRABCROP_CLIP_LABELS未设置文本文件,每行一个标签。
CRABCROP_CLIP_THRESHOLD0.05标签显示在 tags 中所需的最低零样本得分。
CRABCROP_MODERATION_MODEL未设置用于 meta 审核判定的 ViT 分类器 ONNX 模型路径(Falconsai nsfw_image_detection 结构)。请参阅 AI 功能。
CRABCROP_MODERATION_LABELS未设置文本文件,每行一个标签:设置模型时,填写判定模型按输出顺序排列的输出名称;未设置模型时,填写 CLIP 回退方案的零样本提示词标签。
CRABCROP_AESTHETIC_HEAD未设置LAION 风格线性美学头的路径(PyTorch 保存文件),用于对 meta 中的 CLIP 嵌入进行评分。需要设置 CRABCROP_CLIP_*。请参阅 AI 功能。
CRABCROP_DETECT_MODEL未设置用于 blurclass / pixelateclass 和 meta 检测的 YOLOv8 系列 ONNX 模型路径。请参阅 AI 功能。
CRABCROP_DETECT_LABELS未设置类别名称文件,每行一个名称,按索引顺序排列,用于不含 names 元数据的检测模型导出文件。
CRABCROP_DETECT_THRESHOLD0.5最低检测得分。
CRABCROP_DETECTORS_FILE未设置声明来自自有模型的额外检测器(boxes 和 focal 契约)的 JSON 文件路径。请参阅已声明的检测器。
CRABCROP_ORT_DYLIB未设置ONNX Runtime 共享库的路径,当它不在库搜索路径中时使用。
CRABCROP_FONT_PATH未设置text(...) 叠加滤镜所用 TTF/OTF 字体的路径,启动时加载。未设置时禁用此滤镜。
CRABCROP_VIDEO_ENCODER未设置用于 format(mp4) / format(webm) 的外部视频编码器程序路径,启动时会验证。未设置时禁用视频输出。请参阅视频输出。
CRABCROP_VIDEO_TIMEOUT_SECS30每次视频编码的硬性墙钟时间限制,范围为 1 至 600 秒。
CRABCROP_VIDEO_MAX_MB64每次视频编码的输出上限,单位为 MB,范围为 1 至 1024。达到上限时立即中止编码。
CRABCROP_FALLBACK_IMAGE未设置无法获取或解码源时提供的回退图像 URL 或本地文件路径。请参阅回退图像。
CRABCROP_RESULT_STORAGE_DIR未设置磁盘结果存储目录。未设置时禁用第二级缓存。
CRABCROP_RESULT_BUCKET未设置共享结果存储的 S3 存储桶(多副本的第三级缓存)。未设置时禁用。
CRABCROP_RESULT_PREFIX空用于命名空间隔离共享结果存储条目的对象前缀。
CRABCROP_RESULT_STORAGE_MAX_MB512磁盘结果存储预算,单位为 MB。即使设置了目录,0 也会禁用存储。
CRABCROP_UPLOAD_DIR未设置上传端点使用的本地目录目标。此项与 CRABCROP_UPLOAD_BUCKET 只能设置一个;两者都未设置时会禁用上传(路由返回 404)。如果未设置 CRABCROP_FILE_ROOT,此项也会成为 file:// 源根目录;否则必须位于该目录之下。请参阅上传原始文件。
CRABCROP_UPLOAD_BUCKET未设置上传端点使用的 S3 存储桶目标,沿用源 S3 的端点、区域和环境 AWS 凭据。
CRABCROP_UPLOAD_PREFIX空用于命名空间隔离上传存储桶对象的前缀。
CRABCROP_UPLOAD_MAX_MB10单次上传上限,单位为 MB,范围为 1 至 10(源获取上限:已存储的原始文件必须保持可提供服务)。
CRABCROP_UPLOAD_ALLOW_OVERWRITE关闭允许上传 PUT 替换现有对象。关闭时,名称已存在会返回 409。
启用 CRABCROP_BLOCK_PRIVATE_SOURCES 后,加载器会解析主机并检查其地址,然后将连接固定到已验证的地址,因此实际连接的地址就是检查过的地址(不会出现 DNS 重绑定漏洞)。始终拒绝带有用户信息的源 URL(http://user:pass@host/...):加载器绝不会向源站发送 Basic 凭据。
全服务器默认操作

CRABCROP_DEFAULT_OPS 保存一个操作字符串(语法与 URL 的操作相同,也包括 preset: 引用),服务器会将其添加到每个请求的操作前面,适用于不想写入每个 URL 的统一格式、质量或水印。决策如下:

  • 签名永远不包含默认操作。 默认操作属于运营方配置,在验证后应用,与预设展开相同。
  • 默认操作位于 URL 自带的操作之前;对于单例指令(format、quality、fill、dpr、max_bytes),URL 优先,默认项会从合并后的列表中丢弃。因此,在默认操作下发出的精简请求与完整写明所有操作的相同请求,会产生相同的操作并共享缓存条目。其他所有操作(尺寸、滤镜、水印)都会按顺序叠加应用:默认模糊和 URL 模糊会叠加。
  • 缓存条目以合并后的操作为键,与预设的处理方式完全相同,因此更改默认操作会更改键,旧条目会逐渐过期且不再使用。清除操作也会考虑合并结果:清除某个 URL 时会移除其合并后的条目。
  • 预设限制模式拒绝的内容不变(URL 自带的操作),默认操作仍会应用:它们不是 URL 内容。
  • 缓存组仍由 URL 自带的预设决定;仅由默认操作引用的预设不会将条目加入其缓存组。
轮换密钥

轮换不会使已在外部使用的 URL 失效,分为以下三个步骤:

  1. 开启过渡期。 生成新密钥。将 CRABCROP_SECRET 设为新密钥,并将 CRABCROP_SECRET_KEY_ID 设为其 ID(例如 2026-10);同时将当前密钥移至 CRABCROP_SECRET_PREVIOUS,并将 CRABCROP_SECRET_PREVIOUS_KEY_ID 设为其 ID(例如 2026-08)。重启。现在服务器会验证两个密钥:签名段带有密钥 ID(2026-08:<tag>)的 URL 仅会使用对应密钥进行验证;不带 ID 的 URL 则会使用两个密钥验证,因此在密钥 ID 出现之前生成的所有 URL 也能继续使用。清除签名也会采用相同的路由方式。
  2. 使用新密钥生成签名。 将构建器改为使用新密钥并标注其 ID:TypeScript 中使用 buildURL(..., { keyId: "2026-10" }),Python 中使用 build_url(..., key_id="2026-10"),Rust 中使用 sign_with_key_id / build_url_with_key_id,或在 CLI 中使用 crabcrop sign --key-id 2026-10。从此以后,新的缓存条目会使用新密钥。
  3. 结束过渡期。 等旧密钥生成的 URL 已从缓存和页面中过期后,移除 CRABCROP_SECRET_PREVIOUS 并重启(结果缓存的 max-age 为一年,但实际过期时间取决于你自己的 TTL 和页面生命周期)。从此以后,旧密钥将无法验证任何内容:剩余的 URL 会返回 403,这正是轮换的目的。

未知密钥 ID 与错误签名无法区分,这是有意设计的:返回 403,不会透露存在的密钥 ID。

AI 功能

这些功能会在进程内运行 ONNX 模型:removebg(移除背景)、studio(基于相同的遮罩模型合成产品照片,参见 Studio mode)、upscale(超分辨率),以及用于裁剪和遮罩的面部检测。模型是你提供的文件,属于部署构件,类似于此仓库中的 Dockerfile.ai 镜像变体。crabcrop 绝不会在启动时或请求时下载模型。

配置方式如下:

  • 将 CRABCROP_REMOVEBG_MODEL 指向 U²-Net 系列模型文件(输入尺寸为 320×320;U2Netp 导出模型很适合),和/或将 CRABCROP_UPSCALE_MODEL 指向超分辨率模型。放大模型的接口约定是输入动态尺寸的 RGB 图像(值域为 0..1),输出为同一图像的整数倍放大版本;Real-ESRGAN 系列 ONNX 导出模型符合此约定。
  • 将 CRABCROP_FACE_MODEL 指向 YuNet 模型,让 crop:...:smart 以检测到的人脸为中心(按面积加权计算检测到的人脸中心)。人脸检测只会改进智能裁剪:没有模型、未检测到人脸或检测失败时,都会回退到基于细节的焦点;未配置模型的智能裁剪行为与之前完全相同。同一模型也支持显式的人脸操作:crop:...:face(围绕最大的人脸裁剪;检测失败或未检测到人脸时回退到智能裁剪窗口),以及 blurfaces/pixelatefaces 脱敏滤镜。这些操作需要配置模型;未配置时会返回 501,检测失败时会以 500 拒绝脱敏请求,而不会返回未脱敏图像。同一请求中的多个人脸操作会共用一次检测。
  • 将 CRABCROP_CLIP_* 这组变量指向一对 CLIP 模型(带投影头的图像和文本 ONNX 导出模型)、其 tokenizer.json,以及一个每行包含一个标签的文件。这样,meta 响应会增加 tags 和 alt_text:根据处理后的图像计算零样本标签分数,并用得分最高的三个标签生成简短替代文本。标签只会在启动时嵌入一次,因此 meta 请求只会运行图像编码器,且同样受推理并发限制;标签处理失败时会省略这些字段,而不会导致响应失败。标签会按原文进行嵌入,因此短语式标签(aerial view of a beach)和单词标签一样适用。
  • 将 CRABCROP_MODERATION_MODEL 指向 ViT 分类器导出模型(Falconsai nsfw_image_detection 结构:输入为 224x224 图像,输出为一行 logits)。选择 moderation=1 的 meta 请求会增加一个 moderation 对象,其中包含模型对各类别的判定分数;这些分数会经过 softmax 转换为概率,并附带模型标识。CRABCROP_MODERATION_LABELS 用于按输出顺序为模型输出命名,每行一个标签;启动时会检查标签数量是否与模型输出数量一致。未设置该变量时,双输出模型会使用 normal 和 nsfw 作为标签;其他输出数量会导致启动错误。如果未设置模型,则该标签文件会改为指定由 CLIP 模型对生成零样本提示词标签分数,并作为审核 signals 报告,而非判定结果。判定结果仅作标注:服务时不会拦截、模糊处理或更改任何内容;审核失败或未配置时,会在字段中报告不可用,而不会导致 meta 请求失败。请将分数视为模型意见,而非事实:参考模型的训练数据未公开,也没有发布任何子群体评估;此外,NSFW 分类器普遍会对泳装、医学图像和艺术作品过度标记。根据分数采取行动前,请先在自己的语料库上验证。
  • 将 CRABCROP_AESTHETIC_HEAD 指向 LAION 风格的线性美学评分头(PyTorch 保存文件,例如适用于 ViT-B/32 编码器的 sa_0_4_vit_b_32_linear.pth),meta 响应便会增加一个 aesthetic 对象,其中包含该评分头对 CLIP 嵌入的评分,以及评分头和编码器的标识。评分头必须与配置的编码器匹配:若评分头基于 768 维编码器(ViT-L/14),却用于 512 维嵌入(ViT-B/32),则每个请求都会返回空评分,并附带 reason: "dimension_mismatch" 以及预期和实际维度;这不会导致启动失败。格式错误的评分头文件则会导致启动错误,与其他已配置资源相同。同一嵌入也可单独获取:选择 embeddings=1 的 meta 请求会增加 embedding(以 base64 打包的 little-endian float16),以及 embedding_model、embedding_dim、embedding_encoding 和 embedding_normalized,可直接存入向量索引或 DAM。评分仅作描述,不会作为门槛:它体现了 LAION 美学数据所代表的审美倾向,而该数据本身依据的是对以传统摄影为主的作品所作的人类评分,因此会偏好清晰、明亮、构图传统的照片,并低估许多优秀作品。未经自己语料库验证,请勿据此排序、过滤或定价。
  • 将 CRABCROP_DETECT_MODEL 指向 YOLOv8 系列检测模型导出文件,即可启用另外两项功能。类别脱敏滤镜 blurclass(<class>) 和 pixelateclass(<class>) 会脱敏指定类别的所有检测对象(可重复使用滤镜以处理更多类别;匹配时不区分大小写)。这些滤镜遵循人脸脱敏规则:未配置模型时返回 501,未知类别返回 400,检测失败时返回 500,不会返回未脱敏响应。选择 detections=1 的 meta 请求还会返回检测框,方便调试。类别词汇来自模型自身的 names 元数据,Ultralytics 导出文件包含此元数据;未包含元数据的导出文件则可通过 CRABCROP_DETECT_LABELS 指定类别,每行一个,按索引顺序排列。输出格式不符合 YOLOv8 布局的模型会在启动时被拒绝。
  • 模型会在服务器报告就绪前于启动时加载。已配置但无法加载的模型、缺失的文件或错误的形状都会导致启动错误,并显示一行诊断信息,而不会等到首次请求时才意外失败。放大模型会在加载时使用 16×16 测试图像探测其原生放大倍数;不会按整数倍放大的模型会在启动时被拒绝。蒙版模型也会在加载时进行测试:输出不是 320x320 蒙版字段的导出模型会在启动时被拒绝,而不会等到首次 removebg 请求时才失败。
  • 未配置模型时,对应滤镜处于关闭状态:使用该滤镜的请求会明确失败并返回 501(removebg is not configured on this server、upscale is not configured on this server)。不会静默回退,upscale 也绝不会退化为普通缩放。
  • 推理会受到全服务器范围的并发限制,同时最多运行 2 次推理,因此大量 AI 请求不会占用正常图像转换流程的资源。
已声明的检测器

除了一个内置检测模型外,CRABCROP_DETECTORS_FILE 还可以指向一个 JSON 文件,用于声明更多你自己的模型,并让它们遵循两种内置契约。该文件仅包含配置:crabcrop 不会加载或运行操作员代码,而是将每个具名模型接入它已实现的契约。

[
  {
    "name": "crates",
    "model": "/models/crates.onnx",
    "kind": "boxes",
    "labels": "/models/crates.txt",
    "threshold": 0.4
  },
  {
    "name": "logos",
    "model": "/models/logos.onnx",
    "kind": "boxes",
    "labels": ["nike", "adidas", "puma"]
  },
  {
    "name": "saliency",
    "model": "/models/saliency.onnx",
    "kind": "focal"
  }
]
  • name(必填)用于标识声明:可使用字母、数字、_、-;名称重复会导致启动错误,builtin 是内置检测器的保留名称(元检测报告其检测框时会将其作为来源名称)。model(必填)是 ONNX 文件路径。kind(必填)为 "boxes" 或 "focal";其他值会导致启动错误,并指出对应声明。
  • 该文件的配置有上限:最多 1 MiB,最多 16 条声明(每条声明都会在启动时加载模型,并将检测结果加入每个元请求,因此数量上限也限制了这两项开销)。每条声明的类别词汇表最多包含 4096 个类别,内联标签每个最多 256 个字符。
  • boxes 是 YOLOv8 系列检测器,使用与 CRABCROP_DETECT_MODEL 相同的后处理方式进行解码。其类别词汇表来自声明中的 labels(标签文件路径,文件中每行一个类别,按索引顺序排列;也可以是类别名称的内联数组),如果未提供,则使用模型的 names 元数据;两者都存在时,以声明的标签为准。threshold(0 到 1)设定分数下限,默认值为 0.5,与内置检测器相同。如果两种来源都没有提供类别词汇表,或模型输出无法由解码器读取,boxes 声明都会导致启动错误。
  • focal 是热图模型:有一个固定的 [1, C, H, W] 输入(亮度通道时 C 为 1,RGB 时为 3),最多包含 409,600 个像素(检测契约的帧大小为 640x640;输入张量和启动探测会根据声明的形状分配,因此声明更大的帧会导致启动错误),且仅有一个形状为 [H, W]、[1, H, W] 或 [1, 1, H, W] 的输出。焦点取热图中值最高的单元格;如果有多个并列,则取按行优先顺序排列的第一个。此类型不适用 labels 和 threshold。
  • 类别脱敏按固定顺序解析类别的来源:先查内置检测器,再按文件顺序查已声明的 boxes 检测器;第一个词汇表中包含该类别的来源会运行。串联的类别过滤器仍会在每个请求中对每个来源只运行一次。
  • 元检测会汇总所有已配置检测器的结果:先列出内置检测器的检测框,再按文件顺序列出各已声明检测器的检测框;每个检测框都带有 detector 字段,注明其来源("builtin" 或声明的名称)。某个检测器失败时,只会省略它自己的检测框。
  • 智能裁剪按以下顺序查询焦点来源:调用方提供的 focal: 矩形、面部模型、第一个已声明的 focal 来源,最后是内置显著性估计。只会查询第一个 focal 声明;后续声明仍会在启动时加载并探测,但之后不会使用。
  • 与其他模型一样,每条声明都会在启动时加载并探测:模型文件缺失、声明文件无法读取或格式错误、类型未知,或输出不符合契约,都会导致启动错误,并指出对应声明。
  • 在原生倍率为 2× 的服务上使用 upscale(4) 时,会组合运行两次模型,得到精确的请求尺寸;如果原生倍率不能整除请求倍率,则最后会进行高质量缩放以补齐差值。即使模型只接收 RGB,放大过程中仍会保留源图像的透明度。
  • ONNX Runtime 库本身来自 CRABCROP_ORT_DYLIB、标准变量 ORT_DYLIB_PATH,或者在两者都未设置时来自系统库搜索路径。 此仓库中的 Dockerfile.ai 变体展示了预期的结构:基础镜像不包含 AI 负载,第二层则添加固定版本的 ONNX Runtime 1.31.0、U2Netp 模型和 YuNet 模型,并在构建镜像时分别校验哈希;同时通过设置上述变量启用这些功能。按照相同方式添加你自己的超分辨率模型,并设置 CRABCROP_UPSCALE_MODEL 以启用 upscale。

迁移

以下指南介绍如何迁移现有 URL 模板:

  • 从 Thumbor 迁移:概念和操作映射,以及 convert-thumbor 命令,用于机械地转换并重新签名 URL。
  • 从 imgproxy 迁移:逐项选项映射表。
  • 从 Cloudinary 迁移:转换映射,以及资源变为源站 URL 后的变化。
  • 从 imgix 迁移:逐参数映射表,包括翻译后含义会改变的调整比例。
  • 从 Cloudflare Images 迁移:交付模式和选项映射,涵盖从命名变体和灵活变体到预设和签名操作的转换,包括翻译后含义会改变的调整比例。
  • 从 ImageKit 迁移:转换链和参数映射,涵盖从命名转换到预设的转换,包括翻译后含义会改变的值。
  • 从 Gumlet 迁移:逐参数映射表,涵盖从源站默认参数到预设的转换,包括翻译后含义会改变的调整比例。

部署

此仓库提供了多阶段 Dockerfile(运行时镜像包含二进制文件、CA 证书,以及用于解码 AVIF 的 dav1d 库):

docker build -t crabcrop:latest .
docker run -p 3000:3000 -e CRABCROP_SECRET=your-secret crabcrop:latest

对于 AI 功能,请基于该镜像构建 Dockerfile.ai;参见 AI 功能。

在 Kubernetes 上,deploy/helm 是 Helm chart,deploy/kustomize 则是相同工作负载的 kustomize 形式(包括基础配置、用于预设、磁盘层和 AI 变体的组件,以及单副本、多副本和完整配置);deploy/compose 使用 Docker Compose 运行服务。无需 Kubernetes 时,deploy/ecs-fargate、deploy/cloud-run 和 deploy/container-apps 提供各云平台的部署方案(任务定义、服务清单和应用清单,均包含密钥配置和冒烟检查清单)。若在源站前部署 CDN,deploy/cloudflare 提供 Cloudflare 方案(缓存规则、将缓存组映射到缓存标签的清除配置、格式协商和过期注意事项),deploy/cloudfront 提供 CloudFront 方案(遵循源站标头的缓存策略、按路径和缓存标签清除缓存,以及用于规范化 Accept 的边缘函数),deploy/fastly 提供 Fastly 方案(由源站缓存标头负责控制,将缓存组映射到 surrogate key 以按组清除缓存,并关闭 Image Optimizer),deploy/bunnycdn 提供 BunnyCDN 方案(由源站 Cache-Control 负责控制,通过中间件脚本将缓存组映射到 CDN 标签以按组清除缓存,并关闭 Bunny Optimizer),deploy/akamai 提供 Akamai 方案(通过 Caching 行为让源站缓存标头负责控制,使用各预设规则将缓存组映射到缓存标签以按组清除缓存,并关闭 Image and Video Manager)。

在生产环境中,请在反向代理或 CDN 后运行 crabcrop。应用会限制自身的工作量(处理和 AI 门控会在过载时拒绝请求并返回 503),但连接层面的防护应由边缘层负责:请求和标头超时、连接数限制、速率限制以及 TLS 终止。请在代理处将 /health 和 /metrics 限制为仅内部网络可访问:这两个端点均无需身份验证(响应带有 Cache-Control: no-store),而且指标会公开流量和错误计数器。

开发

cargo build
cargo test

AVIF 源解码需要启用 avif-decode cargo 功能(默认关闭,在 Docker 镜像和发布构建中开启;请参阅上文的源格式说明)。启用该功能进行构建时,需要通过 pkg-config 使用 dav1d 1.3 或更高版本,或使用 vendored 构建:

SYSTEM_DEPS_DAV1D_BUILD_INTERNAL=always cargo build --features avif-decode

在本地运行服务只需要一个密钥:

CRABCROP_SECRET=local-dev-secret cargo run

安全

发现安全漏洞?请私下报告——请参阅 SECURITY.md。请勿针对安全问题创建公开议题、拉取请求或讨论。

许可证

MIT。请参阅 LICENSE。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。