Author edit plans with your own LLM¶
Use this when your model, in your harness, should author the edits. videopython supplies the tool schema and a refine loop; you supply the model.
If you would rather videopython run the model, see Let a local LLM edit for you. If your agent should call the tools itself, see Drive editing from an MCP agent. The reasoning behind all three is in LLM-first design.
The loop in three calls¶
from videopython.editing import VideoEdit
schema = VideoEdit.json_schema()
plan = call_your_llm(schema=schema, prompt="Create a 15s highlight reel from input.mp4")
edit = VideoEdit.from_dict(plan)
edit.validate() # dry run over metadata; no frames touched
edit.run_to_file("output.mp4")
Pass the schema to your model¶
VideoEdit.json_schema() describes the plan and its LLM-exposed operations. Give
that schema, the source paths, and source metadata to your integration. The
call_your_llm function above is a placeholder for your own model call; videopython
does not install a hosted-provider SDK.
If your decoder accepts a closed structured-output schema, use:
from videopython.editing import Operation, VideoEdit
plan_schema = VideoEdit.json_schema(strict=True)
operation_schema = Operation.json_schema(strict=True)
See the schema reference for the generated structure. Decoder support varies. Parse and validate the returned plan even when the model accepts the schema: numeric bounds and cross-field rules still need checks.
Refine a plan the model got wrong¶
Build metadata keyed by the exact source paths, then repair and check the plan:
from videopython.base import VideoMetadata
edit = VideoEdit.from_dict(plan)
source_metadata = {
str(segment.source): VideoMetadata.from_path(segment.source)
for segment in edit.segments
}
edit, repairs = edit.repair(source_metadata, clamp_segment_end=True)
edit, dim_repairs = edit.normalize_dimensions(source_metadata, "largest")
errors = edit.check(source_metadata)
if errors:
... # re-prompt with the previous plan and the structured errors
else:
edit.run_to_file("output.mp4")
Clamping segment ends can shorten the requested edit. Omit clamp_segment_end=True
if the caller must decide how to handle an overrun. Signatures, failure behavior,
and metadata requirements are in the
validation reference.
Surface what was changed:
for err in errors:
print(f"{err.code} at {err.location}: {err.field}={err.value} (limit {err.limit})")
for r in repairs:
print(f"{r.code}: {r.location}.{r.field} {r.old} -> {r.new}")
Branch on err.code — a small enum — not on prose. What each method will and will not
touch, and why parsing is deliberately permissive about numbers, is explained in
the plan lifecycle.
Let the model discover the operations¶
Instead of hardcoding an op list in your prompt:
from videopython.editing import Operation, OpCategory
for op_id, cls in Operation.llm_registry().items():
doc = (cls.__doc__ or "").splitlines()[0].strip()
print(f"{op_id}: {doc}")
transforms = {k: v for k, v in Operation.llm_registry().items()
if v.category is OpCategory.TRANSFORM}
llm_registry() is the LLM-safe subset; registry() is everything, for the worker that
executes a stored plan. For per-op tool definitions:
tools = [{
"name": f"transform_{op_id}",
"description": (cls.__doc__ or "").splitlines()[0],
"input_schema": cls.llm_json_schema(), # drops llm_hidden advanced fields
} for op_id, cls in Operation.llm_registry().items()
if cls.category is OpCategory.TRANSFORM]
Use cls.llm_json_schema() rather than cls.model_json_schema() for anything the model
sees — it strips advanced fields such as raw font paths, whose LLM-facing counterpart is
the font name enum.
Include AI operations¶
AI ops register when their classes are imported. Import the classes before generating the schema if your plans may use them:
from videopython.ai import FaceTrackingCrop, ObjectDetectionOverlay
from videopython.editing import VideoEdit
schema = VideoEdit.json_schema() # now includes them
Supply context data¶
Operations that need bulky side-channel input declare it via
requires: ClassVar[tuple[str, ...]]; the runner pulls the matching keys out of
context and re-bases time-based values onto each segment's local timeline.
edit.run_to_file("out.mp4", context={"transcription": transcription})
needs_transcript = [op_id for op_id, cls in Operation.registry().items()
if "transcription" in cls.requires]
Notes¶
- Lead with the schema. It describes the available operations and structural rules.
- Give the model source metadata — duration, dimensions, fps — or it will invent time ranges that do not exist.
- Always
validate()beforerun_to_file(). Inspect structured errors before spending time on rendering. - Re-prompt with the whole error list from
check(), not one error at a time.