跳转至

高级用法

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)

图像格式自动检测

对于 bytesPathLike 输入,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)实现惰性加载和自动分页,这意味着:

  1. 按需加载 — 数据只有在遍历时才会请求 API
  2. 内存高效 — 不会一次性将所有数据加载到内存
  3. 自动分页 — 无需手动处理分页逻辑
# 惰性遍历 — 只在需要时请求下一页
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 连接自动关闭

等价于:

client = search.client(token="...")
try:
    projects = list(client.projects())
finally:
    client.close()

缓存属性

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}")