Skip to content

videoindex (Python)

import videoindex as vi

idx = vi.Index.create("./talks.vidx", config=vi.Config.from_file("videoindex.toml"))
job = idx.add("talk.mp4", policy="coarse_local")
for ev in job.progress():
    print(ev["type"], ev.get("stage"), ev.get("fraction"))
report = job.wait()

for h in idx.search("hybrid retrieval", k=5):
    print(h["video_id"], h["t0"], h["t1"], h["evidence"][0]["text"])

for ev in idx.ask("When do they discuss evaluation?"):
    if ev["type"] == "token":
        print(ev["text"], end="")
    elif ev["type"] == "citation":
        print(f" [{ev['t0']:.0f}s]", end="")

grid = idx.view(video_id, t0=1830, t1=1860, fps=1)   # {"png": bytes, "width", "height", "timestamps"}
frame = idx.frame(video_id, t=1834.5)                # numpy HxWx3 uint8

Operators and policies in Python

An indexing stage can be a Python function. It receives items from the operators before it (frames as NumPy arrays) and returns rows the core stores and hands on, with ids, provenance and caching handled like any built-in stage:

@vi.operator(id="brightness", inputs=["hashed"], outputs=["description"])
def brightness(ctx, item):
    return {"kind": "description", "target_kind": "frame",
            "target_id": item["sample_id"],
            "text": f"mean brightness {item['frame'].mean():.0f} at {item['t']:.0f}s"}

idx.register_operator(brightness)
idx.add("talk.mp4", policy={"coarse": ["sample", "phash", "thumbnail", "brightness"]})

Input and output kinds are the core's item kinds (media, frame, hashed, speech_range, transcript_span, shot, ocr_span, scene, chapter, description, ...). Rows may be transcript_span, ocr_span, shot, scene, chapter or description, with times in seconds. Subclass vi.Operator for stateful operators with a finish(ctx) hook. A policy given as a dict runs just those operators; a string names one from the config.

An agent strategy is a class with next_step(state):

class TwoSearches(vi.Policy):
    def next_step(self, state):
        if len(state["steps"]) < 2:
            return {"tool": "search", "args": {"query": state["question"], "k": 5}}
        return None            # let the LLM write the answer from the observations

answer = idx.ask("...", policy=TwoSearches()).collect()

Build locally with maturin develop --release inside a virtualenv. maturin develop copies the compiled _core.abi3.so into python/videoindex/; it is a build artefact (about 440 MB with the workspace's release debug info, far less when stripped) and is git-ignored, never committed. Distributable wheels come from maturin build --release --strip (what CI runs) (needs the ffmpeg development libraries, pkg-config and clang, like the Rust crates).