main
anastasia Premiere Pro Bridge 0.1.0: Rust bridge, UXP plugin, CEP helper, skills, macOS manifest 32fb583 10d ago

name: premiere-bridge-dev description: Maintainer guide for the Premiere bridge source (Rust server + UXP plugin + CEP helper) — architecture, the verb table, the dev loop on a real Mac, and every premierepro API behavior measured on Premiere 26.5 (time bases, selection lifetime, item types, exporter quirks). Read before changing any premiere_* verb. user-invocable: false

Premiere bridge — developer guide

THE RULE: the AI reads verb OUTPUT, not this file

Every verb response gets _hint / _next / pitfalls stamped from src/catalog.rs. When you add or change a verb, update its catalog row in the same commit. Failure hints come from error_hint() keyed by errorCode, so throw fail(code, message) in the plugin with a stable code.

Layout

Path What
src/main.rs axum server: /health, POST /command (JSON-only, 415/405 guards, no ACAO), /ws (plugin sockets, token in query). Routes verbs by runtime.
src/catalog.rs The verb table: runtime (Local/Uxp/Cep/PreferCep), timeout, summary, args, hint, next, pitfalls. Drives premiere_describe and decoration.
src/host.rs Local verbs: status/readiness/launch/quit, preview_frame + contact_sheet (inline base64, waits for async file writes), export-preset discovery, monitor_snapshot.
src/plugin_pkg.rs Embeds plugin/ with include_str!, builds the .ccx, installs via UPIA; copies the CEP helper + sets PlayerDebugMode.
src/ab.rs Minimal HTTP client for Adom Bridge's direct API (ADOM_DIRECT_API_URL or ~/.adom/direct-api-port).
plugin/uxp/index.js Every UXP verb (handlers), helpers, connection loop.
plugin/cep/ Invisible CEP extension + host.jsx (ES3) for QE razor, speed, tracks, raw ExtendScript.

Protocol (both runtimes): plugin → {type:"hello", runtime:"uxp"|"cep", …}; bridge → {type:"call", id, verb, args}; plugin → {type:"result", id, ok, result|error, errorCode}; plugin → {type:"event", kind} (export progress, project opened). The bridge pings every 15 s.

The plugin finds the bridge by reading ~/.adom/premiere-bridge/connection.json (port + token) on every reconnect attempt, with a copy baked into the .ccx as a fallback.

Dev loop (needs the desktop connected over adom-bridge)

scripts/dev-sync.sh                     # tar → send_files → cargo build --release on the Mac → restart test server :47911
scripts/pb.sh premiere_install_plugin '{"force":true}'   # reinstall plugin; Premiere hot-reloads it (no restart)
scripts/pb.sh premiere_status
scripts/seq.sh                          # compact timeline dump
scripts/pb.sh premiere_eval '{"code":"return Object.keys(ppro.Constants)"}'   # probe the live API

Test edits in a scratch project (premiere_new_project under ~/adom-dev), never in the user's project; close it with close_project {save:false} after checking get_project.path.

Measured API facts (Premiere 26.5.2): don't relearn these

  • Actions must be built inside project.lockedAccess, synchronously. Do all async reads first, then tx(p, label, () => [actions]).
  • TrackItemSelection is only valid inside createEmptySelection's callback. Using it later throws "The script object is no longer valid". Use withSelection(clips, sel => …).
  • ppro.Constants.ProjectItemType doesn't exist. Use ppro.ProjectItem.TYPE_* (CLIP=1, BIN=2, ROOT=3, FILE=4).
  • TickTime.timeToTimecode / timecodeToTime don't exist. The plugin computes timecode itself (drop-frame at 29.97/59.94).
  • Constants.MediaType values are GUID strings; TrackItemType.CLIP = 1; TransitionPosition START=1, END=2.
  • createSequenceFromMedia(name, items) wants plain ProjectItems. Passing undefined as the bin throws "Illegal Parameter type", so omit the argument.
  • Trim semantics: createSetStartAction and createSetInPointAction are both head trims that preserve (start − in). createSetEndAction / createSetOutPointAction are tail trims. createMoveAction(delta) shifts. A cloned item (createCloneTrackItemAction) is offset like a move, which breaks sync. That's why razor is implemented as tail-trim + temporary source in/out + overwrite.
  • Keyframe positions are in the clip's media time (timeline − clip start + in point). The bridge converts from sequence time (kfTime). Enabling time-varying adds a key at the clip's in point.
  • Volume: Volume > Level is linear; 0 dB = 0.17783 (10^((dB-15)/20)).
  • Motion > Position is normalized [0..1, 0..1].
  • Exporter.exportSequenceFrame resolves before the file is fully written (the bridge waits for a stable size). It does NOT evaluate keyframes between two keys. Pass the filename WITH its extension; no doubled extension on 26.5.
  • EncoderManager.exportSequence(…, IMMEDIATELY, …) returns after the render, with no encoder events.
  • A UPIA install of the .ccx hot-loads into a running Premiere, and the plugin's JS runs without the panel being opened.
  • UPIA rejects a manifest with host as an array and no icons ("uxp manifest file is invalid", status −267). Keep host an object and ship icons.
  • CEP helper needs PlayerDebugMode=1 (CSXS 9–13) and only loads at Premiere start.

Adding a verb

  1. Add the handler in plugin/uxp/index.js (or ADOM_VERBS in host.jsx for CEP).
  2. Add its row to VERBS in src/catalog.rs with a real hint/next/pitfalls.
  3. Re-generate bridge.json verbs (see the publish skill), rebuild, reinstall the plugin, and test on a scratch project.
  4. Bump PLUGIN_VERSION / BUNDLED_VERSION and the manifest version together when the plugin changes.