immich 脚本
为指定人物创建一个属于他的共享相册。
#!/usr/bin/env python3
import requests
import sys
import argparse
import os
from dotenv import load_dotenv
# 加载 .env
load_dotenv()
IMMICH_URL = os.getenv("IMMICH_SERVER_URL", "").rstrip("/")
API_KEY = os.getenv("IMMICH_API_KEY", "")
if not IMMICH_URL or not API_KEY:
print("❌ 请在 .env 文件中设置 IMMICH_SERVER_URL 和 IMMICH_API_KEY")
print("示例:")
print("IMMICH_SERVER_URL=http://192.168.31.10:3001")
print("IMMICH_API_KEY=your_real_key")
sys.exit(1)
HEADERS = {
"Accept": "application/json",
"x-api-key": API_KEY,
}
def get_all_people():
"""获取所有人脸人物(兼容包装格式 { "people": [...] })"""
url = f"{IMMICH_URL}/api/people"
resp = requests.get(url, headers=HEADERS, timeout=10)
resp.raise_for_status()
data = resp.json()
# 如果返回的是 { "people": [...] },提取列表
if isinstance(data, dict) and "people" in data:
people_list = data["people"]
elif isinstance(data, list):
# 兼容旧版直接返回数组的情况
people_list = data
else:
raise ValueError(f"❌ 无法识别 /api/people 的返回格式。返回内容: {data}")
# 确保结果是列表
if not isinstance(people_list, list):
raise ValueError("❌ 'people' 字段不是列表")
return people_list
def find_person_by_name(people, name):
for person in people:
if name.lower() in (person.get("name") or "").lower():
return person
return None
def search_assets_by_person_id(person_id):
"""通过 timeline 接口获取人物所有照片(适配 v1.138.1 扁平化响应)"""
all_asset_ids = []
# Step 1: 获取时间桶列表
buckets_url = f"{IMMICH_URL}/api/timeline/buckets"
params = {
"personId": person_id,
"visibility": "timeline"
}
resp = requests.get(buckets_url, headers=HEADERS, params=params)
resp.raise_for_status()
buckets = resp.json()
if not buckets:
print("⚠️ 该人物没有任何照片")
return []
print(f"📅 发现 {len(buckets)} 个时间桶,开始加载...")
# Step 2: 遍历每个时间桶
for bucket in buckets:
time_bucket = bucket["timeBucket"]
bucket_url = f"{IMMICH_URL}/api/timeline/bucket"
params2 = {
"personId": person_id,
"timeBucket": time_bucket,
"visibility": "timeline"
}
resp2 = requests.get(bucket_url, headers=HEADERS, params=params2)
resp2.raise_for_status()
data = resp2.json()
# ✅ 关键修正:直接从 data["id"] 获取 asset ID 列表
asset_ids = data.get("id", [])
if not isinstance(asset_ids, list):
asset_ids = [] # 安全兜底
all_asset_ids.extend(asset_ids)
print(f" 📂 {time_bucket[:7]}: 获取 {len(asset_ids)} 张")
print(f"✅ 总共找到 {len(all_asset_ids)} 张包含该人物的照片/视频")
return all_asset_ids
def create_shared_album(album_name):
"""创建共享相册(新版路径:/api/albums)"""
url = f"{IMMICH_URL}/api/albums"
payload = {
"albumName": album_name,
"description": f"Auto-generated for: {album_name}",
"shared": True
}
resp = requests.post(url, headers=HEADERS, json=payload, timeout=10)
resp.raise_for_status()
album = resp.json()
print(f"✅ 相册 '{album_name}' 创建成功,ID: {album['id']}")
return album["id"], album.get("shareLink")
def add_assets_to_album(album_id, asset_ids):
"""向相册添加资产(Immich v1.138.1 正确方式)"""
if not asset_ids:
print("⚠️ 没有资产需要添加到相册")
return
# ✅ 注意路径是 /api/albums/{id}/assets,方法是 POST
url = f"{IMMICH_URL}/api/albums/{album_id}/assets"
payload = {"ids": asset_ids}
try:
resp = requests.put(url, headers=HEADERS, json=payload, timeout=60)
resp.raise_for_status()
print(f"✅ 成功向相册添加 {len(asset_ids)} 张照片")
except requests.exceptions.HTTPError as e:
print(f"❌ 添加资产失败 (HTTP {e.response.status_code})")
print("错误详情:", e.response.text)
sys.exit(1)
def main(target_name, album_name=None):
print(f"🔍 查找人物: '{target_name}'")
try:
people = get_all_people()
except requests.exceptions.ConnectionError:
print(f"❌ 无法连接 Immich: {IMMICH_URL}")
sys.exit(1)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print("❌ API 密钥无效,请检查 IMMICH_API_KEY")
else:
print(f"❌ API 错误: {e}")
sys.exit(1)
person = find_person_by_name(people, target_name)
if not person:
print(f"❌ 未找到 '{target_name}',可用人物:")
for p in people:
print(f" - {p.get('name', 'Unnamed')}")
sys.exit(1)
print(f"👤 找到: {person['name']} (ID: {person['id']})")
asset_ids = search_assets_by_person_id(person["id"])
if not asset_ids:
print("⚠️ 该人物暂无照片")
sys.exit(0)
if not album_name:
album_name = f"{person['name']}的照片"
album_id, share_link = create_shared_album(album_name)
add_assets_to_album(album_id, asset_ids)
print("\n🎉 完成!")
if share_link:
print(f"🔗 分享链接: {share_link}")
else:
print(f"🔗 备用链接: {IMMICH_URL}/share/{album_id}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="分享 Immich 中某个人物的所有照片")
parser.add_argument("name", help="人物名称(模糊匹配)")
parser.add_argument("-a", "--album", help="自定义相册名")
args = parser.parse_args()
main(args.name, args.album)
评论