Immich 支持的媒体格式全解:从 MIME 类型表到上传校验、库扫描与元数据提取的源码剖析
Immich 对"哪些文件算照片、哪些文件算视频、哪些文件会被自动索引"的判定,全部集中在一张服务端 MIME 类型表里。本文以官方文档列出的图片/视频支持清单为骨架,结合 mime-types.ts 的完整定义、上传校验与库扫描的实现代码,讲清楚每个格式在 Immich 中的实际处理路径——读完你可以准确回答"某个相机 RAW 文件能否直接上传"、"HEIF 照片的方向信息从哪里来"、"为什么某些格式 Web 端不能直接预览"这类问题。
一、图片格式支持清单
官方文档给出的图片格式支持情况如下(完整清单以源码为准,见 mime-types.ts):
| 格式 | 扩展名 | 支持 | 备注 |
|---|---|---|---|
AVIF |
.avif |
支持 | HEIF 家族,可能含动画 |
BMP |
.bmp |
支持 | |
GIF |
.gif |
支持 | 可能含动画 |
HEIC |
.heic |
支持 | HEIF 家族 |
HEIF |
.heif |
支持 | HEIF 家族 |
JPEG 2000 |
.jp2 |
支持 | |
JPEG |
.jpeg .jpg .jpe .insp |
支持 | .insp 同样映射为 image/jpeg |
JPEG XL |
.jxl |
支持 | |
MPO |
.mpo |
支持 | Multi-Picture,多张图片合并的格式,MIME 类型按 image/jpeg 处理 |
PNG |
.png |
支持 | 可能含动画(APNG) |
PSD |
.psd |
支持 | Adobe Photoshop |
RAW |
.raw |
支持 | |
RW2 |
.rw2 |
支持 | |
SVG |
.svg |
支持 | |
TIFF |
.tif .tiff |
支持 | |
WEBP |
.webp |
支持 | 可能含动画 |
源码中的完整图片清单:比文档表格多出的部分
文档表格只列出了"常见"格式,而源码中的 image 映射(由 webSupportedImage、webUnsupportedImage 与 raw 三张表合并而成)实际上还包含:
- 30 种相机厂商 RAW 扩展名,均映射为厂商专属或
x-前缀的 MIME 类型,例如.arw/.sr2/.srf(索尼)、.cr2/.cr3(佳能)、.nef/.nrw(尼康)、.dng(Adobe)、.x3f(适马)、.3fr/.fff(哈苏)等,完整定义见 mime-types.ts#L4-L35。这意味着主流相机厂商的 RAW 文件都可以直接作为资产上传,而不是仅支持文档表格中列出的.raw、.rw2两种; .hif(小米手机的 HEIF 变体),同样被列入 HEIF 家族;.vob对应的视频扩展名(见下文视频章节,文档表格中未列出)。
一个值得注意的细节:文档标注为"RAW"的 .raw 在源码中对应 image/raw 与 image/x-panasonic-raw 两个 MIME 候选(mime-types.ts#L28),即它是松下(Panasonic)的 RAW 格式,而索尼 .arw、尼康 .nef 等各自独立登记。
Web 浏览器能否直接预览:webSupported 与 webUnsupported 的划分
源码把图片显式分成两组(mime-types.ts#L37-L70):
webSupportedImage:.avif.bmp.gif.jpeg/.jpg.png.webp——浏览器原生可渲染,Web 端可直接显示原图;webUnsupportedImage:所有 RAW 扩展名、.heic/.heif/.hif、.jp2.jxl.svg.tif/.tiff、.mpo、.insp等——浏览器无法直接解码。
这解释了一个实际使用中的现象:上传 HEIC/HEIF 或 RAW 照片后,Web 界面展示的是服务端生成的 JPEG/WebP 预览图,而非直接加载原文件。isWebSupportedImage() 与 isHeifImage() 这两个导出方法就是围绕这两个集合构建的(mime-types.ts#L157-L158)。
二、视频格式支持清单
文档列出的视频格式如下:
| 格式 | 扩展名 | 支持 |
|---|---|---|
3GPP |
.3gp .3gpp |
支持 |
AVI |
.avi |
支持 |
FLV |
.flv |
支持 |
M4V |
.m4v |
支持 |
MATROSKA |
.mkv |
支持 |
MP2T |
.mts .m2ts .m2t .ts |
支持 |
MP4 |
.mp4 .insv |
支持(.insv 映射为 video/mp4) |
MPEG |
.mpg .mpe .mpeg |
支持 |
MXF |
.mxf |
支持 |
QUICKTIME |
.mov |
支持 |
WEBM |
.webm |
支持 |
WMV |
.wmv |
支持 |
源码中 video 映射与文档一致(mime-types.ts#L106-L127),并额外登记了 .vob(video/mpeg,DVD 光盘常见的 MPEG 程序流)。两个与文档不同的实现细节值得留意:
.mxf的 MIME 类型是application/mxf而非video/前缀,因此assetType()在判定资产类型时必须把它显式并入视频分支:
// server/src/utils/mime-types.ts
if (contentType === 'application/mxf' || contentType.startsWith('video/')) {
return AssetType.Video;
}
.avi登记了 4 个历史 MIME 候选(video/avi、video/msvideo、video/vnd.avi、video/x-msvideo),.mp4/.insv均归入video/mp4。lookup()始终取数组第一个候选作为规范 MIME 类型,其余候选仅用于toExtension()的反向查找。
XMP 侧车文件:文档之外的重要第三类
mime-types.ts 中还定义了 sidecar 类别(mime-types.ts#L129-L131):
const sidecar: Record<string, string[]> = {
'.xmp': ['application/xml', 'text/xml'],
};
.xmp 是 RAW 照片的 EXIF 侧车文件,与图片本身同名同目录。Immich 把侧车文件作为独立的上传类别处理(详见 XMP Sidecars 文档),并在元数据提取时优先采用侧车中的日期信息。
三、格式判定如何贯穿整个服务端
mimeTypes 模块导出的判定函数是格式体系的"消费者",理解它们就能理解 Immich 对文件类型的全部处理逻辑(mime-types.ts#L147-L181):
| 方法 | 作用 |
|---|---|
isAsset(filename) |
是否为受支持资产(图片或视频) |
isImage / isVideo / isRaw |
单类别判定 |
isWebSupportedImage |
浏览器能否直接渲染 |
isHeifImage |
是否属于 .avif/.heic/.heif/.hif 家族 |
isPossiblyAnimatedImage |
是否可能含动画帧(.avif .gif .heic .heif .jxl .png .webp) |
isProfile(filename) |
是否可作为头像(.avif .dng .heic .heif .jpeg .jpg .png .svg .webp) |
isSidecar(filename) |
是否 .xmp 侧车文件 |
canBeTransparent(filename) |
格式是否具备透明通道能力(.avif .bmp .gif .heic .heif .hif .jxl .png .svg .tif .tiff .webp) |
lookup(filename) |
文件名 → MIME 类型,未识别时回退为 application/octet-stream |
toExtension(mimeType) |
MIME 类型 → 扩展名,其中 image/jpeg 固定返回 .jpg(见 extensionOverrides) |
assetType(filename) |
归一为 Image / Video / Other 三类资产 |
getSupportedFileExtensions() |
返回全部受支持扩展名列表,供库扫描与存储过滤使用 |
上传校验:不支持的格式在入口即被拒绝
上传接口按字段名区分三类上传内容,并在 canUploadFile() 中做格式门禁(asset-media.service.ts#L59-L89):
switch (fieldName) {
case UploadFieldName.ASSET_DATA: {
if (mimeTypes.isAsset(filename)) {
return true;
}
break;
}
case UploadFieldName.SIDECAR_DATA: {
if (mimeTypes.isSidecar(filename)) {
return true;
}
break;
}
case UploadFieldName.PROFILE_DATA: {
if (mimeTypes.isProfile(filename)) {
return true;
}
break;
}
}
this.logger.error(`Unsupported file type ${filename}`);
throw new BadRequestException(`Unsupported file type ${filename}`);
也就是说:资产上传必须命中图片或视频表,.xmp 只能通过侧车通道上传,头像则受更严格的 profile 白名单限制——例如你不能用 .tiff 或 .mpo 设置头像。判定基于文件扩展名(内部通过 getFilenameExtension() 取扩展名并转小写后查表),而非嗅探文件头。
库自动索引:扩展名列表驱动文件监控
当启用库(Library)的文件监控时,Immich 用全部受支持扩展名动态构造 picomatch 匹配器(library.service.ts#L103-L106):
const matcher = picomatch(`**/*{${mimeTypes.getSupportedFileExtensions().join(',')}}`, {
nocase: true,
ignore: library.exclusionPatterns,
});
匹配成功的文件变更会入队 LibrarySyncFiles 任务进行索引,未匹配的(如 .pdf、.txt)则直接忽略;同时库级排除模式 exclusionPatterns 仍优先生效。同理,存储层查询也基于同一份扩展名列表构造过滤条件(storage.repository.ts#L286)。这保证了一个关键的一致性:上传接口、库扫描、存储查询看到的"受支持格式"永远是同一张表,不会各走各的。
四、特殊格式的元数据提取逻辑
格式清单定义了"能不能进",而进入之后不同格式还需要差异化处理。metadata.service.ts 中有两处与格式直接相关的分支:
HEIF 家族的方向(Orientation)修正
// don't use Exif Orientation for HEIF based images, it's usually missing or invalid.
// prefer irot (ExifTool QuickTime:Rotation) mapped to ExifOrientation.
if (mimeTypes.isHeifImage(asset.originalPath)) {
const orientation = this.getHeifOrientation(mediaTags);
...
}
(metadata.service.ts#L604-L613)HEIC/HEIF/AVIF/HIF 照片的 Exif Orientation 标签通常缺失或不可靠,Immich 改从 QuickTime 的 irot 标签推导方向,推导不出则直接删除该标签,避免按错误方向渲染手机拍的照片。
动画图像与时长(Duration)标签
// prefer duration from video tags
// don't save duration if asset is definitely not an animated image (see e.g. CR3 with Duration: 1s)
if (videoResult || !mimeTypes.isPossiblyAnimatedImage(asset.originalPath)) {
delete mediaTags.Duration;
}
(metadata.service.ts#L595-L599)视频资产的时长一律以容器探测结果为准;对"可能是动画"的图片(AVIF/GIF/HEIC 等)保留 Exif 侧的时长;而对 .tiff、.cr3 这类静态格式则丢弃 Duration 标签——注释中明确提到佳能 CR3 会带一个无意义的 Duration: 1s。
RAW 嵌入缩略图的提取
RAW 文件内部通常嵌入了一张贴图用 JPEG。服务端通过管理端配置项 image.extractEmbedded("Extract embedded",见 config.dto.ts#L348)控制是否对 mimeTypes.isRaw(...) 为真的资产执行嵌入 JPEG 提取(media.service.ts#L259 与 media.service.ts#L412),用于在无法解码 RAW 时仍有可显示的预览。
五、测试如何锁定这张格式表
mime-types.spec.ts 对上述实现做了系统性约束:
- 按排序的 MIME 与扩展名映射表逐条断言
lookup结果(如{ mimetype: 'image/cr3', extension: '.cr3' }、{ mimetype: 'video/mp2t', extension: '.m2t' }等约百条用例),新增或改动格式必须同步更新测试; - 断言
image/video/sidecar/profile各表的键与值全部小写,且video、sidecar表的键必须保持排序(mime-types.spec.ts#L214-L217); - 验证
image/前缀纯度(image表不允许混入非图片 MIME)、canBeTransparent的正反例集合、isPossiblyAnimatedImage对动画/静态/视频三类输入的判定,以及toExtension('image/jpeg')固定返回.jpg的覆盖规则。
六、小结
Immich 的格式支持可以用三层来概括:
- 单一事实来源:mime-types.ts 中
raw(30 种厂商 RAW)、webSupportedImage/webUnsupportedImage(浏览器可渲染与否)、video、sidecar四张表合并出全部判定依据; - 统一消费:上传门禁(asset-media.service.ts)、库文件监控与索引(library.service.ts)、存储过滤(storage.repository.ts)全部复用同一组
mimeTypes.*方法; - 差异化处理:HEIF 家族的方向修正、动画图像的时长保留、RAW 嵌入 JPEG 提取,均在元数据/媒体服务中按格式分支处理,并有 mime-types.spec.ts 锁定行为。
如果你需要确认某个扩展名是否受支持,最快路径就是直接查 mime-types.ts 中的表——文档表格是其常见子集,源码表才是完整清单。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00