高级用法¶
ImageLike 类型¶
ImageLike 是一个联合类型,允许您使用多种格式的图像数据进行多模态搜索。SDK 内部会自动将所有格式转换为字节数据上传。
支持的类型¶
| 类型 | 需要的库 | 说明 |
|---|---|---|
str / PathLike |
— | 文件路径 |
bytes / bytearray |
— | 原始图像字节 |
PIL.Image |
Pillow | Pillow 图像对象 |
numpy.ndarray |
numpy, Pillow | NumPy 数组(BGR 格式) |
cv2.Mat |
opencv-python | OpenCV 图像矩阵 |
torch.Tensor |
torch, numpy, Pillow | PyTorch 张量 |
tf.Tensor |
tensorflow, numpy, Pillow | TensorFlow 张量 |
jax.ndarray |
jax, numpy, Pillow | JAX 数组 |
cupy.ndarray |
cupy, numpy, Pillow | CuPy GPU 数组 |
xarray.DataArray |
xarray | xarray 数据数组 |
使用示例¶
# 文件路径
results = project.search(query="...", image="photo.png")
# 字节数据
with open("photo.png", "rb") as f:
results = project.search(query="...", image=f.read())
# PIL Image
from PIL import Image
img = Image.open("photo.png")
results = project.search(query="...", image=img)
# NumPy 数组
import numpy as np
arr = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
results = project.search(query="...", image=arr)
# OpenCV
import cv2
img = cv2.imread("photo.png")
results = project.search(query="...", image=img)
# PyTorch Tensor
import torch
tensor = torch.randn(480, 640, 3)
results = project.search(query="...", image=tensor)
图像格式自动检测¶
对于 bytes 和 PathLike 输入,SDK 会根据文件头魔数自动检测图像格式:
| 格式 | 魔数 |
|---|---|
| PNG | \x89PNG\r\n\x1a\n |
| JPEG | \xff\xd8 |
| GIF | GIF87a / GIF89a |
| WebP | RIFF....WEBP |
| ICO | \x00\x00\x01\x00 |
| BMP | BM |
| TIFF | II*\x00 / MM\x00* |
无法识别时默认按 PNG 处理。
惰性加载与分页¶
SDK 中大多数返回集合数据的方法都使用 Python 生成器(Generator)实现惰性加载和自动分页,这意味着:
- 按需加载 — 数据只有在遍历时才会请求 API
- 内存高效 — 不会一次性将所有数据加载到内存
- 自动分页 — 无需手动处理分页逻辑
# 惰性遍历 — 只在需要时请求下一页
for video in project.videos():
if video.name == "target":
break # 找到目标后停止,不会请求多余的页
# 获取前 N 个结果
import itertools
first_5 = list(itertools.islice(project.search(query="..."), 5))
# 转为完整列表(会请求所有页)
all_videos = list(project.videos())
使用生成器的方法¶
| 方法 | 模型 | 分页大小 |
|---|---|---|
client.projects() |
Project |
默认 |
project.videos() |
Video |
默认 |
project.characters() |
Character |
50 |
project.reference_images() |
ReferenceImage |
全量 |
project.mangas() |
Manga |
全量 |
project.novels() |
Novel |
全量 |
project.metadata() |
Metadata |
全量 |
project.search(...) |
Clip |
100 |
video.clips() |
Clip |
100 |
上下文管理器¶
SearchClient 实现了上下文管理器协议,推荐使用 with 语句确保资源正确释放:
from aimage import search
with search.client(token="...") as client:
projects = list(client.projects())
for project in projects:
videos = list(project.videos())
# 退出 with 块后 HTTP 连接自动关闭
等价于:
缓存属性¶
Video 模型使用 @cached_property 实现懒加载缓存。首次访问时发起 API 请求,之后使用缓存值:
video = next(project.videos())
# 首次访问 — 发起 API 请求
extra = video.extra
print(extra.resources)
# 后续访问 — 使用缓存,不再请求 API
extra_again = video.extra # 直接返回缓存
缓存属性列表:
| 属性 | 类型 | 触发的 API |
|---|---|---|
video.extra |
VideoExtra |
GET /videos/{id} |
video.parsed |
VideoParsed |
GET /videos/{id}/processed-files |
video.script_data |
ScriptData | None |
下载脚本 JSON |
video.koubanhyou_data |
Koubanhyou | None |
下载香盘表 JSON |
资源下载¶
SDK 中的多个模型都提供资源下载功能,统一遵循以下模式:
save(path) / save_bytes()¶
| 模型 | save 方法 | save_bytes 方法 |
|---|---|---|
Video |
save(path) |
save_bytes() |
Clip |
save_clip(path) / save_thumbnail(path) |
save_bytes_clip() / save_bytes_thumbnail() |
Character |
save_image(path) / save_reference_images(folder) |
save_bytes_image() / save_bytes_reference_images() |
ReferenceImage |
save(path) |
save_bytes() |
VideoResource |
save(path) |
save_bytes() |
Manga |
save(path) |
save_bytes() |
Novel |
save(path) |
save_bytes() |
VideoParsed |
save_script_data(path) / save_prop_list_data(path) |
save_bytes_script_data() / save_bytes_prop_list_data() |
批量下载示例¶
import os
output_dir = "output"
os.makedirs(output_dir, exist_ok=True)
with search.client(token="...") as client:
for project in client.projects():
proj_dir = os.path.join(output_dir, project.name)
os.makedirs(proj_dir, exist_ok=True)
# 下载所有角色图片
char_dir = os.path.join(proj_dir, "characters")
os.makedirs(char_dir, exist_ok=True)
for char in project.characters():
if char.image_url:
char.save_image(os.path.join(char_dir, f"{char.name}.jpg"))
# 下载所有参考图片
ref_dir = os.path.join(proj_dir, "references")
os.makedirs(ref_dir, exist_ok=True)
for ref_img in project.reference_images():
ref_img.save(os.path.join(ref_dir, ref_img.file_name))
# 下载视频片段缩略图
for video in project.videos():
thumb_dir = os.path.join(proj_dir, f"S{video.season_number}E{video.episode_number}")
os.makedirs(thumb_dir, exist_ok=True)
for clip in video.clips():
if clip.thumbnail_url:
clip.save_thumbnail(os.path.join(thumb_dir, f"clip_{clip.index:04d}.jpg"))
完整工作流示例¶
import os
import dotenv
from aimage import search
from aimage.search.settings import SearchService
from aimage.search.models.type import SearchMode, SceneType, ObjectSize
dotenv.load_dotenv()
with search.client(
token=os.getenv("AIMAGE_SEARCH_TOKEN"),
service=SearchService.PROD,
) as client:
# 1. 获取项目
projects = list(client.projects())
project = projects[0]
print(f"项目: {project.name}")
# 2. 查看角色列表
characters = list(project.characters())
for char in characters:
print(f" 角色: {char.name} ({'主角' if char.is_protagonist else '配角'})")
# 3. 场景搜索 + 角色过滤
results = project.search(
query="打斗",
include_characters=[characters[0]],
object_size=ObjectSize.CLOSE_SHOT,
scene_type=SceneType.NORMAL,
)
for clip in results:
print(f" 片段: {clip.start_time:.1f}s - {clip.end_time:.1f}s")
# 4. 台词精确搜索
results = project.search(
query="ありがとう",
search_mode=SearchMode.SUBTITLE,
subtitle_exact_match=True,
)
for clip in results:
print(f" 台词: {clip.subtitle}")
# 5. 查看视频详情和剧本数据
video = next(project.videos())
if video.script_data:
print(f" 剧本: {video.script_data.title}")
for s in video.script_data.scripts[:3]:
print(f" Cut {s.cut}: {s.screen}")