Skip to content

Live Resolve SDK

The live editor integration is part of the installed praisonai-clips package. The Python API uses the standard library; MCP is an optional adapter supplied by praisonai-clips[live]. Importing the SDK does not launch or connect to Resolve.

For the tested Mac setup and all troubleshooting findings, read the shared skill runbook.

Install and connect

.venv/bin/pip install -e '.[live]'
.venv/bin/praisonai-resolve status
.venv/bin/praisonai-resolve timeline
.venv/bin/praisonai-resolve-mcp

praisonai-resolve-mcp is a stdio MCP server; it waits for a client. praisonai-resolve mcp is equivalent. The Console transport ships its Lua and AppleScript resources in the wheel. It needs no repository-local data/ files, npm, upstream checkout or menu-script installer. macOS Accessibility permission, an unlocked desktop and an open Resolve project are needed for live operations.

Python API

from praisonai_shorts_studio.live import ClipTarget, Framing, ResolveClient

resolve = ResolveClient()
status = resolve.status()
items = resolve.timeline_items(track_index=1)
# Choose the intended clip after inspecting names and identities.
target = ClipTarget(**items[0]["target"])
original = resolve.get_framing(target)

change = resolve.set_framing(
    target,
    zoom_x=1.25,
    zoom_y=1.25,
    pan=40,
    crop_left=20,
)
resolve.save_project(target.project_id)

# When restoration is intended:
resolve.restore_framing(target, original)
resolve.save_project(target.project_id)

set_framing accepts partial changes and returns target, before and after. The values in those snapshots use the same field names as Framing:

Field Native property Validation
zoom_x, zoom_y ZoomX, ZoomY Finite, positive
pan, tilt Pan, Tilt Finite
crop_left, crop_right CropLeft, CropRight Finite, nonnegative
crop_top, crop_bottom CropTop, CropBottom Finite, nonnegative

Pass both zoom axes for uniform scaling. Invalid and unknown fields fail before contacting the desktop. Native Resolve can impose further constraints.

A ClipTarget contains project, timeline and clip UUIDs, plus video track and clip indices. The native Lua invocation checks all of them before the setter. A reordered clip or switched project/timeline fails instead of editing whatever happens to occupy the previous position. Fetch a fresh target after structural edits. The SDK does not create native undo/redo behavior.

Prepare a quiet-pause review timeline

plan = resolve.plan_silence(target, threshold_db=-25, min_silence=0.3, padding=0.08)
print(plan["removed_ranges"], plan["removed_seconds"])
if plan["removed_ranges"]:
    result = resolve.apply_silence_plan(plan, timeline_name="Quiet pauses review")
    print(result["review"])

plan_silence analyzes the clip locally with FFmpeg. apply_silence_plan verifies source identity, creates a new timeline, preserves clip framing and timeline resolution, and adds corresponding video and audio segments. It currently requires an unretimed 30 fps source and timeline. A quiet passage may include speech: inspect and listen to the review timeline. Errors can leave a partially created timeline; inspect Resolve before retrying. The original timeline is not edited.

Agents can call the plan_silence and apply_silence_plan MCP tools. The CLI and YAML reference exposes the same options and SDK behavior.

Frame exports and official API extensions

from pathlib import Path

resolve.export_frame(target.project_id, Path("data/review/new-frame.png"))
resolve.save_project(target.project_id)  # save after exports

result = resolve.run_lua(
    "return guarded_project:GetCurrentTimeline():GetName()",
    expected_project_id=target.project_id,
)
print(result["result"])

Frame export accepts a new .png path and refuses to overwrite an existing file. The parent directory is created locally. It exports the current timeline frame; seek or inspect that timeline first. The project guard does not select a clip or imply permission for unrelated operations in an arbitrary Lua program.

run_lua returns the bounded ok/result/prints envelope. Its project UUID guard runs in the same native invocation. There is a 64 KB response cap. A Lua error may follow partial changes; the escape hatch is not a transaction.

The equivalent CLI accepts a file:

praisonai-resolve run-lua /absolute/path/to/operation.lua --project-id PROJECT_UUID

Modular design and extension points

live/
  resolve.py              ResolveClient: shared native operations and guards
  models.py               ClipTarget, Framing and one native property mapping
  lua.py                  Lua string/number encoding and response validation
  errors.py               Shared operation and transport failures
  transports/
    __init__.py           LuaTransport protocol
    macos_console.py      AppleScript transport and serialization lock
  resources/              Packaged AppleScript, pinned Lua, license, provenance
  mcp.py                  Thin MCP bindings to ResolveClient
  mcp_client.py           Optional reusable stdio MCP client
  cli.py                  Command line entry point using the same SDK

A new transport only implements:

from typing import Any

class MyLuaTransport:
    def execute(self, code: str) -> dict[str, Any]:
        # Deliver Lua through the selected host and return its result envelope.
        raise NotImplementedError

resolve = ResolveClient(transport=MyLuaTransport())

The contract is an envelope such as {"ok": true, "result": {...}, "prints": []}. Return the native result unchanged; the client validates failures and truncation. A custom transport must preserve ordering and prevent interleaving with other clients controlling the same host. Raise ResolveTransportError when delivery or the outcome is uncertain. No transport should automatically retry a possibly executed mutation.

New editor operations belong in ResolveClient, using the shared Lua encoder, project guard and clip guard where applicable. Add a thin MCP binding only when needed. This keeps SDK, CLI and MCP behavior consistent. Existing scripts remain compatibility wrappers; add no new native operation implementations there.

The Mac transport accepts state_dir and timeout constructor arguments. Use one state directory across clients of the same Resolve instance to share its nonblocking lock. timeout bounds the host subprocess; the Console resource also has a 30-second reply deadline and 10-second AppleEvent timeouts. A timeout can leave a native operation running; inspect Resolve before retrying. Normal and AppleScript-error exits restore the clipboard; an external process kill can interrupt that cleanup. The transport is intended for short live interactions.

Validation

.venv/bin/pytest -q tests/test_resolve_sdk.py
.venv/bin/python scripts/test_resolve_mac_live.py \
  --project-id PROJECT_UUID --timeline-name 'PraisonAI Live Crop Review'

The regression suite exercises generated Lua in Resolve's bundled standalone interpreter when available, rejects stale and injected identities before setters, round-trips Lua strings, checks input validation, and verifies no timeout retry. It makes no GUI calls. Interpreter-dependent tests skip where that interpreter is absent.

The opt-in live test requires an explicitly identified disposable timeline with one video clip. It uses the real stdio MCP server and records native framing and decoded RGB frame comparisons. Original framing is restored in finally and the project is saved last. Read its generated report for the actual result.