Skip to main content

MCP tool for adding image overlays to an OTIO timeline.

Project description

clipwright-overlay

MCP tool that annotates an OTIO timeline with a static image overlay (logo, watermark, lower-third graphic, end card) for materialisation by clipwright-render.

Overview

clipwright-overlay writes an image_overlay marker to the first video track (V1) of an OTIO timeline. The marker stores the image path, position, scale, opacity, and timing. clipwright-render reads the marker and inserts the image as an extra -i input, building an FFmpeg filter chain that composites the image onto the video during the single render pass.

Design principle (separation of annotation and realisation): clipwright-overlay writes only the OTIO; it does not invoke FFmpeg or touch media files. All image compositing is deferred to clipwright-render.

Prerequisites

  • Python 3.11 or later
  • clipwright core package (shared types, envelope, OTIO utils)
  • No FFmpeg is needed at annotation time. FFmpeg is only invoked by clipwright-render at realisation time.

Note — clipwright-render >= 0.10.0 required for materialisation. clipwright-overlay only annotates the OTIO timeline; it never invokes FFmpeg. The actual image compositing is performed by clipwright-render. Image overlay support was added in clipwright-render 0.10.0 — earlier versions will ignore the image_overlay markers and produce output without the overlay. Install or upgrade before calling clipwright_render on an annotated timeline:

pip install "clipwright-render>=0.10.0"

MCP Tool: clipwright_add_overlay

Parameters

Name Type Default Description
timeline string required Input OTIO timeline file path (.otio). The parent directory is the co-location root for the image file.
output string required Output OTIO timeline file path (.otio). Must differ from timeline.
image_path string required Path to the overlay image. Must be co-located under the output timeline's parent directory (see Co-location Constraint). Supported formats: .png, .jpg, .jpeg, .webp.
start_sec float required Start time in seconds (≥ 0) when the overlay becomes visible.
duration_sec float required Duration in seconds (> 0) that the overlay remains visible.
x string "(W-w)/2" Horizontal position expression (FFmpeg overlay filter syntax). W = base video width, w = overlay width. Default centres the overlay horizontally.
y string "(H-h)/2" Vertical position expression. H = base video height, h = overlay height. Default centres the overlay vertically.
scale float 1.0 Overlay scale factor relative to the original image size. Range: (0, 8]. 1.0 = original size.
opacity float 1.0 Opacity of the overlay. Range: [0.0, 1.0]. 1.0 = fully opaque.
fade_in_sec float 0.3 Fade-in duration in seconds (≥ 0). 0.0 = no fade-in (overlay appears immediately).
fade_out_sec float 0.3 Fade-out duration in seconds (≥ 0). 0.0 = no fade-out. fade_in_sec + fade_out_sec must not exceed duration_sec.

Return value

{
  "ok": true,
  "summary": "Added image overlay 'logo.png' at 5.0s for 10.0s. Timeline now has 1 image overlay(s). Output: output.otio.",
  "data": {
    "applied": 1,
    "overlay_count": 1,
    "start_sec": 5.0,
    "duration_sec": 10.0
  },
  "artifacts": [{"role": "timeline", "path": "/absolute/path/to/output.otio", "format": "otio"}],
  "warnings": []
}

When an identical overlay is submitted again (same parameters), applied returns 0 and a warning is added; no duplicate marker is written (idempotency).

Error codes (annotation time)

Code Cause
INVALID_INPUT start_sec < 0, duration_sec ≤ 0, scale ≤ 0 or scale > 8, opacity outside [0, 1], fade_in_sec < 0, fade_out_sec < 0, or fade_in_sec + fade_out_sec > duration_sec
INVALID_INPUT image_path extension is not .png, .jpg, .jpeg, or .webp
INVALID_INPUT image_path or x / y expression contains a control character or a prohibited character (: ; [ ] , ')
INVALID_INPUT output path is identical to timeline path
INVALID_INPUT Timeline already has 64 image_overlay markers (per-timeline limit)
FILE_NOT_FOUND image_path does not exist
PATH_NOT_ALLOWED image_path is outside the output timeline's parent directory tree
UNSUPPORTED_OPERATION The input timeline has no V1 video track

Error codes (render time, raised by clipwright-render)

Code Cause
SUBPROCESS_FAILED The overlay image could not be decoded by FFmpeg (corrupt file or unsupported encoding). Message shows the image basename only (CWE-209). Hint: "The overlay image may be corrupt or an unsupported format; provide a valid .png/.jpg/.jpeg/.webp."

Co-location Constraint

The image_path must be located under the same directory as the output .otio file (or in a recursive subdirectory of it).

Why this rule exists: clipwright-render enforces the same co-location boundary for all resources referenced in an OTIO timeline (sources, subtitles, image overlays). If the image were outside that boundary, the annotation would succeed but render would immediately fail with PATH_NOT_ALLOWED.

By enforcing co-location at annotation time, clipwright-overlay guarantees that any .otio it produces will pass through clipwright-render without a PATH_NOT_ALLOWED error.

Relative-path storage (V2-3 round-trip portability): The image path is stored in the OTIO marker as a POSIX relative path from the output timeline's parent directory (e.g. images/logo.png). When clipwright-render reads the marker, it reconstructs the absolute path using the timeline file's parent directory as the base. This means projects remain portable when the entire directory tree is moved or copied to another location, as long as the relative positions of the timeline and image files are preserved.

project/
  logo.png           ← allowed (same directory, stored as "logo.png")
  assets/
    watermark.png    ← allowed (subdirectory, stored as "assets/watermark.png")
  output.otio        ← output timeline
/other/path/logo.png  ← PATH_NOT_ALLOWED

Position Expressions

x and y accept FFmpeg overlay filter expressions. The following variables are available at render time:

Variable Meaning
W Base video width in pixels
H Base video height in pixels
w Overlay image width (after scaling)
h Overlay image height (after scaling)
main_w Alias for W
main_h Alias for H
overlay_w Alias for w
overlay_h Alias for h

Common position examples

Position x y
Centre (W-w)/2 (default) (H-h)/2 (default)
Top-left (10 px margin) 10 10
Top-right (10 px margin) W-w-10 10
Bottom-left (10 px margin) 10 H-h-10
Bottom-right (10 px margin) W-w-10 H-h-10
Bottom-centre (W-w)/2 H-h-10

Allowed characters in x / y: letters, digits, _, (, ), +, -, *, /, ., and space. Characters : ; [ ] , ' and control characters are prohibited to prevent FFmpeg filtergraph injection.

readOnlyHint Rationale

clipwright_add_overlay carries readOnlyHint=true in its MCP annotations.

clipwright-overlay writes only a new .otio file; the input media, the input timeline, and the image file are never modified. The new-file write is outside the readOnly scope (consistent with clipwright-sequence, clipwright-trim, clipwright-silence, and other annotation tools). This signals to AI orchestrators that the tool is safe for speculative execution and automatic retry without side effects.

Fade Chain

The opacity and fade effect are implemented in a single filter chain per overlay:

[{N}:v]scale=iw*{scale}:-2,format=rgba,colorchannelmixer=aa={opacity},
fade=t=in:st={start}:d={fade_in}:alpha=1,fade=t=out:st={end-fade_out}:d={fade_out}:alpha=1[ov{i}];
{base}[ov{i}]overlay=x='{x}':y='{y}':enable='between(t,{start},{end})'[outvimg{i}]
  • scale=iw*{scale}:-2 — scale the image width by scale; height is computed automatically with even rounding (-2) for yuv420p compatibility.
  • format=rgba — add an alpha channel to the image.
  • colorchannelmixer=aa={opacity} — set constant opacity (aa accepts a constant double only; time-varying expressions are not supported by FFmpeg).
  • fade=t=in:...:alpha=1 — ramp the alpha from 0 to 1 over fade_in_sec. When fade_in_sec == 0 this segment is omitted entirely (no degenerate d=0 filter).
  • fade=t=out:...:alpha=1 — ramp the alpha from 1 to 0 over fade_out_sec. Omitted when fade_out_sec == 0.
  • The fade:alpha=1 flag multiplies the existing alpha channel, so the effective alpha ramps from 0 → opacity → 0 across the fade windows.
  • overlay=x='{x}':y='{y}':enable='between(t,{start},{end})' — composite the prepared image onto the base video within the time window. x / y are single-quoted (consistent with enable and drawtext).

This chain is inserted after the drawtext filter, so image overlays appear on top of text overlays.

Two-Phase Workflow

clipwright_add_overlay(timeline, output, image_path, ...)   # Phase 1 — annotate OTIO
        │
        ▼  OTIO timeline with image_overlay marker
clipwright_render(timeline, output_media)                   # Phase 2 — composite and encode
        │
        ▼  video with image/logo composited

Stacking multiple overlays

Multiple calls accumulate overlays on the same timeline:

# Add a channel logo (top-right, always visible)
r1 = await session.call_tool("clipwright_add_overlay", {
    "timeline":      "/project/edit.otio",
    "output":        "/project/with_logo.otio",
    "image_path":    "/project/assets/logo.png",
    "start_sec":     0.0,
    "duration_sec":  120.0,
    "x":             "W-w-20",
    "y":             "20",
    "scale":         0.15,
    "opacity":       0.8,
    "fade_in_sec":   0.0,
    "fade_out_sec":  0.0
})

# Add a lower-third graphic at a specific moment
r2 = await session.call_tool("clipwright_add_overlay", {
    "timeline":      "/project/with_logo.otio",
    "output":        "/project/with_logo_lowerthird.otio",
    "image_path":    "/project/assets/lower_third.png",
    "start_sec":     15.0,
    "duration_sec":  5.0,
    "x":             "(W-w)/2",
    "y":             "H-h-80",
    "scale":         0.6,
    "opacity":       1.0,
    "fade_in_sec":   0.3,
    "fade_out_sec":  0.3
})

# Render
render_result = await session.call_tool("clipwright_render", {
    "timeline": "/project/with_logo_lowerthird.otio",
    "output":   "/project/final.mp4"
})

MCP Client Registration

clipwright-overlay does not require FFmpeg at annotation time, so no environment variables are needed in the MCP server entry. Register it in your MCP client configuration (.mcp.json / claude_desktop_config.json):

{
  "mcpServers": {
    "clipwright-overlay": {
      "command": "clipwright-overlay"
    }
  }
}

clipwright-render (which materialises the OTIO into video) still requires CLIPWRIGHT_FFMPEG.

Installation

Within a uv workspace:

uv run --package clipwright-overlay clipwright-overlay

Or install from PyPI:

pip install clipwright-overlay
clipwright-overlay

License

MIT — See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clipwright_overlay-0.1.0.tar.gz (15.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

clipwright_overlay-0.1.0-py3-none-any.whl (17.0 kB view details)

Uploaded Python 3

File details

Details for the file clipwright_overlay-0.1.0.tar.gz.

File metadata

  • Download URL: clipwright_overlay-0.1.0.tar.gz
  • Upload date:
  • Size: 15.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for clipwright_overlay-0.1.0.tar.gz
Algorithm Hash digest
SHA256 65186d76f9c3a21b89a29a5f1e30dc50a2854a71f1ebe616c9449f3b613a89dd
MD5 ae4420bc85ece415cc60da37498e3d98
BLAKE2b-256 b1b5a59c619cc709d664cbebbdcc16ee900ab7fdbcb3475d059e065a998d077d

See more details on using hashes here.

Provenance

The following attestation bundles were made for clipwright_overlay-0.1.0.tar.gz:

Publisher: publish.yml on satoh-y-0323/clipwright

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file clipwright_overlay-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for clipwright_overlay-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f9e433b49afe14a740ea27adf07ebc04a4b050dffe81358fdcf24864cbd18459
MD5 847bf3ad74f8bc2c09868e581ba7d538
BLAKE2b-256 567a4a095ac5b7e860cc4c38d83210140ea62df7d418a9bc53cbac48237e9782

See more details on using hashes here.

Provenance

The following attestation bundles were made for clipwright_overlay-0.1.0-py3-none-any.whl:

Publisher: publish.yml on satoh-y-0323/clipwright

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page