首页
/ Immich Python 脚本上传资产实战:/api/assets 接口、x-api-key 认证与源码级实现解析

Immich Python 脚本上传资产实战:/api/assets 接口、x-api-key 认证与源码级实现解析

2026-09-04 20:59:46作者:申梦珏Efrain

本文以 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.ymldocker/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.tsauthenticate 逻辑会优先读取该请求头(也兼容 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')

两点实践提示:

  1. 时间字段格式:服务端对 fileCreatedAt / fileModifiedAt 使用 ISO 8601 解析(见 server/src/dtos/asset-media.dto.ts 中的 isoDatetimeToDate)。显式调用 .isoformat()(如上文改写)比依赖 requestsdatetime 对象的隐式字符串化更稳妥,能避免分隔符歧义。这两个字段是必填的,上传后资产在 Immich 时间轴上的位置即由 fileCreatedAt 决定。
  2. 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.tsx-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.tsuploadAsset@ApiResponse 声明):

HTTP 状态 含义 响应体示例
201 Created 资产新建成功 {'id': 'ef96f635-...', 'duplicate': False}
200 OK 资产已存在(重复),复用已有 ID {'id': '...', 'duplicate': True}

脚本侧应检查 response.status_code201 表示新入库,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 服务端任何配置。

登录后查看全文
热门项目推荐
相关项目推荐