Immich Python 脚本上传资产实战:/api/assets 接口、x-api-key 认证与源码级实现解析
本文以 Immich 官方指南 Python File Upload 为主体,完整讲解如何用 Python + requests 库通过 REST API 向 Immich 服务器上传照片/视频资产:包括认证头、multipart 请求体各字段的含义与取值、响应状态码的语义,以及服务端从鉴权、去重到流式落盘的完整处理链路。读完你可以独立编写、扩展自己的 Immich 批量上传脚本(例如接入 XMP sidecar、SHA1 去重预检),并理解其背后源码的实现依据。
上传接口概览
Immich 的资产上传入口是 POST {BASE_URL}/assets 端点。官方示例中 BASE_URL = 'http://127.0.0.1:2283/api',其中 2283 是 Immich 服务器(server 容器)的默认对外端口,可以在 docker/docker-compose.yml 与 docker/docker-compose.prod.yml 中的 2283:2283 端口映射处确认。如果你部署时改过端口或走了反向代理,需相应调整。
该端点的认证方式之一是 API Key:在请求头中携带 x-api-key: <YOUR_API_KEY>。从源码看,Immich 在 server/src/enum.ts 中将 ApiKey = 'x-api-key' 定义为标准认证头,server/src/services/auth.service.ts 中 authenticate 逻辑会优先读取该请求头(也兼容 apiKey 查询参数),随后校验该 Key 的哈希并检查其被授予的权限是否覆盖当前路由所需的 AssetUpload 权限——因此你创建 API Key 时必须包含上传(asset upload)权限,否则即使 Key 有效也会被拒绝。
端点定义位于 server/src/controllers/asset-media.controller.ts,其 uploadAsset 方法声明了 @Authenticated({ permission: Permission.AssetUpload, sharedLink: true })、@ApiConsumes('multipart/form-data'),并在 v2 起标记为 stable 版本。
完整示例:官方 Python 脚本
以下是 docs/docs/guides/python-file-upload.md 中的完整示例,保留原结构并补充了关键注释,可直接复制运行(需先 pip install requests):
#!/usr/bin/python3
import requests
import os
from datetime import datetime
API_KEY = 'YOUR_API_KEY' # 替换为在 Immich 设置中创建的、具有上传权限的 API Key
BASE_URL = 'http://127.0.0.1:2283/api' # 按实际部署地址与端口调整
def upload(file):
stats = os.stat(file)
headers = {
'Accept': 'application/json',
'x-api-key': API_KEY # API Key 认证头
}
data = {
# ISO 8601 时间戳:服务端用文件自身的时间作为拍摄/修改时间
'fileCreatedAt': datetime.fromtimestamp(stats.st_mtime).isoformat(),
'fileModifiedAt': datetime.fromtimestamp(stats.st_mtime).isoformat(),
'isFavorite': 'false', # 布尔以字符串形式传递,可选
}
files = {
# multipart 文件字段,字段名必须为 assetData
'assetData': open(file, 'rb')
}
response = requests.post(
f'{BASE_URL}/assets', headers=headers, data=data, files=files)
print(response.json())
# {'id': 'ef96f635-61c7-4639-9e60-61a11c4bbfba', 'duplicate': False}
upload('./test.jpg')
两点实践提示:
- 时间字段格式:服务端对
fileCreatedAt/fileModifiedAt使用 ISO 8601 解析(见 server/src/dtos/asset-media.dto.ts 中的isoDatetimeToDate)。显式调用.isoformat()(如上文改写)比依赖requests对datetime对象的隐式字符串化更稳妥,能避免分隔符歧义。这两个字段是必填的,上传后资产在 Immich 时间轴上的位置即由fileCreatedAt决定。 assetData是约定的字段名:它不是任意值。服务端 multipart 解析时按固定字段名取文件,UploadFieldName枚举在 server/src/dtos/asset-media.dto.ts 中定义为ASSET_DATA = 'assetData'、SIDECAR_DATA = 'sidecarData'。
请求体字段详解
官方示例只用了最小字段集。对照服务端 AssetMediaCreateDto(由 server/src/dtos/asset-media.dto.ts 中的 AssetMediaBaseSchema / AssetMediaCreateSchema 定义),完整参数如下:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
fileCreatedAt |
ISO 8601 日期时间 | 是 | 文件创建时间,决定资产在时间轴中的位置 |
fileModifiedAt |
ISO 8601 日期时间 | 是 | 文件修改时间 |
assetData |
二进制文件 | 是 | 照片/视频本体,multipart 文件字段 |
sidecarData |
二进制文件 | 否 | XMP sidecar 文件,用于携带编辑/位置信息(参见 XMP Sidecars 特性文档) |
isFavorite |
布尔(字符串形式) | 否 | 是否直接标记为收藏 |
duration |
毫秒整数 | 否 | 视频时长,服务端据此记录时长元数据 |
filename |
字符串 | 否 | 文件名 |
visibility |
枚举 | 否 | 资产可见性(如 archive 可归入暗箱) |
livePhotoVideoId |
UUID | 否 | Live Photo 视频 ID,用于关联同组照片 |
metadata |
JSON 数组 | 否 | 额外元数据项,供脚本写入自定义 EXIF/属性 |
从源码注释可以看到,assetData / sidecarData 在 DTO 中仅标记为“文档用途”(文件部分不会进入 JSON body),真正的文件走 multipart 通道由 multer 解析——这也解释了为什么示例中把它们拆到 files 参数而不是 data 参数。
服务端处理链路:鉴权 → 去重 → 流式落盘
请求到达后,控制器按拦截器顺序经过三道处理,理解它们有助于排查上传问题:
1. 认证(AuthGuard)
server/src/middleware/auth.guard.ts 中的 AuthGuard 对所有非 public 路由调用 authService.authenticate。对于 API Key 场景,server/src/services/auth.service.ts 从 x-api-key 头取值后走 validateApiKey 分支:按哈希查库、加载绑定用户,并对该 Key 携带的权限做 isGranted 校验。上传路由要求 Permission.AssetUpload,权限不足的 Key 会得到 401/403 而非 404,排查时可以先用 curl -H "x-api-key: ..." {BASE_URL}/user 验证 Key 本身是否有效。
2. SHA1 去重预检(AssetUploadInterceptor)
server/src/middleware/asset-upload.interceptor.ts 在真正接收文件之前读取请求头中的 SHA1 校验和(ImmichHeader.Checksum,控制器 Swagger 注释描述为“sha1 checksum that can be used for duplicate detection before the file is uploaded”)。如果库中已存在相同校验和的资产,直接返回 200 + DUPLICATE 状态并复用已有资产 id,不上传文件体。批量脚本可以利用这一点:先用本地计算的 SHA1 请求该头,跳过已存在的文件,显著减少网络流量。
3. 流式解析与落盘(FileUploadInterceptor)
server/src/middleware/file-upload.interceptor.ts 基于 multer 的自定义 storage 实现:
- 文件不是先整体缓冲再处理,而是通过
pipeline(file.stream, writeStream, ...)流式写入上传目录,对大视频文件友好; - 写入过程中逐 chunk 更新
sha1哈希并累计size,落盘后得到{ path, size, checksum }; - 空文件会在落盘阶段被拦截为
400 File is empty,控制器层的FileNotEmptyValidator(['assetData'])还会对缺失assetData字段做二次校验; - 客户端中断连接(
ECONNRESET)会触发onUploadError清理临时文件,不会留下脏数据。
响应与状态码
控制器对返回状态有明确约定(见 server/src/controllers/asset-media.controller.ts 的 uploadAsset 及 @ApiResponse 声明):
| HTTP 状态 | 含义 | 响应体示例 |
|---|---|---|
201 Created |
资产新建成功 | {'id': 'ef96f635-...', 'duplicate': False} |
200 OK |
资产已存在(重复),复用已有 ID | {'id': '...', 'duplicate': True} |
脚本侧应检查 response.status_code:201 表示新入库,200 表示命中去重(可能是文件体去重或 checksum 预检拦截)。官方示例中 print(response.json()) 打印的正是 {id, duplicate} 结构,id 是后续调用下载原片(GET /assets/:id/original)、查看缩略图(GET /assets/:id/thumbnail)等接口的关键。
实践要点与适用边界
- 认证前提:API Key 需由管理员/用户创建且包含上传权限;示例假设服务以 HTTP 明文运行在
127.0.0.1:2283,公网部署时应改走反向代理 + HTTPS,避免 Key 与媒体数据明文传输(参考 反向代理配置文档)。 - 时间语义:官方示例直接取
os.stat(file).st_mtime(文件在磁盘上的修改时间)作为创建/修改时间。对于有 EXIF 拍摄时间的照片,更准确的做法是从文件元数据中提取拍摄时间填入fileCreatedAt,否则时间轴排序可能与真实拍摄时间不符。 - 批量与断点:Immich 另提供
POST /assets/bulk-upload-check端点(见控制器中checkBulkUpload方法),接受一组{id, checksum}批量查询哪些文件已存在,比逐个加 checksum 头更适合大规模迁移脚本。 - 格式限制:服务端在 multer
fileFilter阶段调用assetService.canUploadFile对文件做可上传性校验,不支持的格式会直接报 4xx 错误;支持清单可对照 Supported Formats 文档。
综上,这个几十行的 Python 脚本背后是一条完整的“鉴权 → 去重预检 → 流式落盘 → 元数据入库”管线。掌握 assetData 字段约定、x-api-key 权限模型与 checksum 去重机制后,你可以把它扩展为支持 sidecar 同步、视频时长上报(duration 字段)与断点续传的自维护上传工具,而无需改动 Immich 服务端任何配置。
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 StartedRust0623
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