Skip to content

About

Transcript-driven, closed-loop AI editing bridge for Adobe Premiere Pro, built on official UXP APIs

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

Premiere Pro Agent Bridge

A minimal, auditable bridge that lets an AI agent (or any script) drive a live Adobe Premiere Pro session for transcript-driven, closed-loop video editing — built entirely on Premiere's official UXP APIs (GA since 25.6), with fully local Whisper transcription.

Status: experimental (v0.7.1). Tested on Premiere Pro 27.1 / Windows 11 (earlier releases also ran on 26.x). macOS is not supported yet. Forty-seven whitelisted panel operations, checked against one action spec, plus a Python library and CLI on top (edit plans, contact sheets, effect-preserving clip swaps, razor-free cuts, a listening report), reviewable in one sitting.

Get started in 3 steps

  1. Download ppro-agent-bridge-v0.7.1.zip from Releases, right-click it and choose Extract All. (New in 0.7: PremiereAgentBridge-Setup-0.7.1.exe on the same page installs everything, Python included, without admin rights — see Install & run.)
  2. Install: in the extracted folder, double-click 1 Install Premiere Agent Bridge.bat. A window checks your computer, fixes what is missing and puts a Premiere Agent Bridge shortcut on your desktop and in the Start menu.
  3. Start and edit: double-click that shortcut (or 2 Start Premiere Agent Bridge.bat), open Premiere Pro and its panel (Window → UXP Plugins → Agent Bridge). The panel tells you what to send your AI helper (Claude Code or Codex).

中文: 1. 从 Releases 下载 zip,右键「全部解压缩」(0.7 起也可以直接运行同一页的安装程序 PremiereAgentBridge-Setup-0.7.1.exe,自带 Python,不要管理员权限;还没有代码签名,Windows 提示时点「更多信息 → 仍要运行」)。2. 双击 1 Install Premiere Agent Bridge.bat: 它会检查电脑、补齐缺的东西,并在桌面和开始菜单放一个 Premiere Agent Bridge 快捷方式。 3. 双击这个快捷方式(或 2 Start Premiere Agent Bridge.bat),打开 Premiere 和面板 (窗口 → UXP 增效工具 → Agent Bridge),面板会告诉你该对 AI 助手说什么。

From a git clone, the same two steps are setup_bridge.bat and start_bridge.bat in the repository root; details are under Install & run.

Why this exists

Most "AI video editing" agents are open-loop: a prompt goes in, a finished render comes out, and no project file survives in the middle — so a human can never take over, refine, or trust the result. Editing is not a generation task; it is a search task that converges through feedback (cut, look, adjust).

This bridge closes the loop by giving an agent three "eyes" on a real Premiere project, each with its own cost profile:

Layer Artifact Answers Cost
Semantic transcript digest (sentences + timecodes) what is being said, when cheapest — most decisions happen here
Structure OpenTimelineIO export what sits where on the timeline pure arithmetic
Perception rendered program-output frames what it actually looks like most expensive — used sparsely

Decisions are made in the text domain, executed in the structure domain, and spot-checked in the perception domain. The Premiere project file stays at the center, so humans and agents can hand work back and forth at any point.

Architecture

agent / terminal / Python
      |   python cli/ppro.py <action>     (JSON out, exit codes; = python -m ppro_bridge)
      |   python cli/transcribe.py        (local Whisper, no Premiere needed)
      |   from ppro_bridge import ops     (the same commands as functions)
      v
broker/broker.py          WebSocket hub, binds 127.0.0.1 and ::1 (port 8017) ONLY
      ^
      |   UXP panels cannot listen on sockets, so the panel dials OUT (ws://localhost:8017)
      v
plugin/                   UXP panel inside Premiere (whitelisted executor)
      v
Premiere Pro project      every write is a named entry in Undo history
  • ppro_bridge/ — the Python package: transport, item resolution, edit plans, clip swaps, transcripts, one function per CLI command (ops), and actions.json, the action spec. cli/*.py are thin wrappers, so cd cli && python ppro.py … keeps working.
  • plugin/ — main.js loads small modules from plugin/lib/ (core, timeline, project, effects, capabilities, export jobs, handlers, the Start bridge launcher); lib/spec.js is generated from the action spec, so the panel refuses unknown actions and bad params itself. lib/build.js says which build it is: a dev build (true) also registers api_smoke, the test cases that change Premiere; this repository, its release zips and .ccx packages say dev: false.
  • broker/fake_panel.py — an in-memory stand-in that answers every action.

Requirements

  • Adobe Premiere Pro 26.3+ (OTIO export needs 26.2, transcript checks 26.3, Speech-to-Text 26.5); tested on 27.1
  • Windows (macOS untested: Premiere restricts plain-HTTP connections there)
  • Adobe UXP Developer Tool (install via Creative Cloud)
  • Python 3.10+, pip install -r requirements.txt
  • Optional, for local transcription: pip install faster-whisper (GPU: also pip install nvidia-cublas-cu12 nvidia-cudnn-cu12 — the CLI puts their DLLs on the search path automatically)
  • Optional, for the test suite: Node.js (runs the panel code against mocks)
  • Premiere: Settings → Plugins → Enable developer mode, then restart Premiere

Install & run

Installer (new in 0.7). PremiereAgentBridge-Setup-<version>.exe does what the zip's two .bat files do: a per-user install without admin rights into %LOCALAPPDATA%\Programs\Premiere Agent Bridge, with its own Python (python.org's embeddable 3.14 and pinned wheels, every file checked by SHA-256 at build time), Start menu and optional desktop shortcuts, optional start at login, an English / Simplified Chinese wizard, the setup window after install, and an uninstaller in Settings > Apps that keeps %USERPROFILE%\.ppro-agent-bridge unless you say otherwise. python scripts/build_installer.py <release export folder> builds it (it downloads only with --download; Inno Setup 6.5+ compiles it); .github/workflows/release.yml builds it on a version tag and runs the whole test suite under the bundled Python first. It is not code-signed yet (installer/SIGNING.md): Windows SmartScreen says "Windows protected your PC" — More info → Run anyway — and the zip stays. start_bridge.bat and setup_bridge.bat use the bundled python\python.exe when it is there, else the Python on PATH as before. Moving from the zip to the installer: the installer takes over the zip's "Premiere Agent Bridge" shortcuts (same name), and uninstalling it removes them; run the zip's setup_bridge.bat again to get them back.

  1. Once: double-click setup_bridge.bat. The setup opens in its own window (Edge app mode: no address bar or tabs) and goes through the machine step by step: Python, the Python packages, Premiere (at least the panel manifest's minimum, 26.3), UXP Developer Tool and developer mode, then the optional ffmpeg, Pillow and faster-whisper, and last a Premiere Agent Bridge shortcut with the bridge's icon on the desktop and in the Start menu (type the name in Start to find it), starting the bridge with Windows and starting it now. Fixes are buttons, and it checks again whenever you come back to the window. Without Python it opens a page that explains how to install it. setup_bridge.bat --console is the text version with y/n questions (python -m ppro_bridge.doctor; without --interactive it only reports); python -m ppro_bridge.setup_ui --demo shows the window without touching the machine.

  2. Every day: double-click start_bridge.bat (keep its window open). It runs the broker with --load-panel: the panel is loaded into every Premiere that starts — at start-up and after a Premiere restart. No clicking Load. UXP Developer Tool runs only while that happens: the broker notices the new Premiere process, starts UXP Developer Tool minimized, loads the panel, waits until it answers and closes UXP Developer Tool again (its service accepts any connection and can load any folder into Premiere, so it should not stay open; the loaded panel keeps running without it). A UXP Developer Tool you opened yourself is used and left open. Open the panel in Premiere via Window → UXP Plugins → Agent Bridge; the status dot turns green when it is connected to the broker. While no broker runs, the panel shows a Start bridge button that opens start_bridge.bat for you (UXP asks once whether it may). Closing the panel does not disconnect it: on 27.1 its code keeps running.

    Why UXP Developer Tool still has to run: Premiere only connects to a developer service that UXP Developer Tool announces through Adobe's own IPC; a look-alike service is never contacted. The bridge drives UXP Developer Tool's service instead (ppro_bridge/devloader.py, the command-line endpoint Adobe's uxp plugin load used; python -m ppro_bridge.devloader loads once).

    By hand, as before: python broker/broker.py (without --load-panel), then UXP Developer Tool → Add Plugin → plugin/manifest.json → Load.

    Without UXP Developer Tool (not tested yet): python scripts/build_ccx.py packages the panel as a .ccx for Creative Cloud's plugin installer; it prints the install command and runs nothing. See "Installing the panel as a .ccx" in SKILL.md.

  3. Smoke test (with any project open):

    cd cli
    python ppro.py ping
    python ppro.py get_project
    

    ping says "compatible": true when CLI, broker and panel are the same version, the panel speaks protocol 2 and this Premiere build is not on the panel's denylist; otherwise its warning says what to restart or reload.

Pairing is automatic: on its first start the broker writes a random token to %USERPROFILE%\.ppro-agent-bridge\token, and the CLI, any script using ppro_bridge, the MCP server and the Premiere panel prove it on every connection without sending it (see "Security model"). (Not in %LOCALAPPDATA%: Windows gives packaged apps such as the Claude desktop app a private copy of it, so a CLI run from one would read a different token than a broker started by double-click.) A client without it is refused with AUTH_FAILED — that includes a CLI older than 0.5, so run the CLI and the broker from the same version. PPRO_BRIDGE_TOKEN (the token itself) or PPRO_BRIDGE_TOKEN_FILE (another file) override this for the broker and the CLI (the panel reads only the file). The broker also keeps an audit log, one JSON line per request, in %USERPROFILE%\.ppro-agent-bridge\audit\audit-YYYY-MM-DD.jsonl (30 days kept; PPRO_AUDIT_DIR moves it, PPRO_AUDIT_DIR=off turns it off).

No Premiere at hand? python broker/fake_panel.py stands in for the panel so the whole pipeline can be tested headlessly. It keeps an in-memory project and timeline, so apply_plan, list_items, set_scale, replace_clip and remove_items can be exercised end to end. To test without disturbing a live Premiere session, run it against a second broker, in three terminals (Windows cmd shown; PowerShell: $env:PPRO_BRIDGE_PORT = "8018", bash: export PPRO_BRIDGE_PORT=8018):

:: terminal 1
set PPRO_BRIDGE_PORT=8018
python broker/broker.py

:: terminal 2
set PPRO_BRIDGE_URL=ws://127.0.0.1:8018
python broker/fake_panel.py

:: terminal 3
set PPRO_BRIDGE_URL=ws://127.0.0.1:8018
python cli/ppro.py ping

The test suite starts its own broker and fake panel on free ports (synthetic data only, nothing touches Premiere): python -m unittest discover -s tests.

Commands

Command Purpose
ping [--probe] Liveness check + version/protocol handshake (compatible, actions, capabilities, denied); --probe re-checks capabilities
get_project Active project / sequence name and guid
get_sequence Frame size, fps, duration, every track's name and clip count
set_active_sequence Open another sequence (by name or guid) for the next commands
open_project --path x.prproj Open a project, or bring an open one to the front, for the next commands: no dialogs, nothing saved or closed; a file that points at a network path is refused
select_items --track A1 --from 0 --to 60 Select clips in the Timeline for what only Premiere's panels do (Properties › Enhance Audio); --clear
list_items [--track V3] Timeline clips as JSON (sequence start/end, source in/out, media path) — no OTIO round-trip
inspect_items [--track V5] Effect chains (params, keyframes in media time, added filters with occurrence) of timeline clips — what the editor changed
set_params --track V5 --at s --json fx.json [--dry-run] Effect params and keyframes on one clip, with an explicit time base (media, clip, sequence), and keyframe interpolation ("interpolation": new or existing keys); validated as a whole before anything changes. Audio tracks too (A1: the clip's own Volume)
set_volume --track A1 --at s --db -6 Clip volume in dB as Premiere shows it, on one clip or the clips entirely inside --from/--to (--mode overlap: every clip touching it); verified
add_transition --track V5 --at s --match-name NAME --duration s --edge start|end A video transition on a clip's start or end, read back from OTIO; refused where the edit point already has one (TRANSITION_EXISTS)
remove_transition --track V5 --at s --edge start|end [--dry-run] Remove the video transition at a clip's start or end in one undo step; found in and verified against OTIO, saying which clip it lay on
remove_effect --track V5|A1 --at s --match-name NAME [--occurrence n] [--dry-run] Remove one added effect (filter) from a clip in one undo step; built-in Motion / Opacity / volume are refused; the chain is read back
remove_keyframes --track V5|A1 --at s --match-name NAME --param i (--all | --from s --to s --time-base media|clip|sequence) [--keep-value] Remove one param's keyframes in a time range, or all of them (animation off, optionally keeping the clip's middle value); one undo step, read back
replace_clip --track V5 --at s --with "item" [--atrack A3] [--skip-effect NAME] [--dry-run] "Replace With Clip" that keeps effect params, keyframes, filters and the clip's own transitions without touching a neighbour's, verified by read-back; the new audio goes to a free track or the swap is refused, and so is a component the new clip cannot get
list_transition_names Video transition match names
list_project_items Project panel clips with bin and media path
list_transcripts [--jobs] Every project clip + whether it has a transcript, plus Speech-to-Text jobs
transcribe.py <media> --output f.json [--language en] [--model large-v3] Local Whisper → Adobe-format transcript JSON (no cloud, no key)
transcribe_clip --item "name" [--language en-US] [--wait 600] [--output f.json] Premiere Speech-to-Text (26.5+) on one clip, waited for and exported
export_transcript --output f.json [--item "bin/name"] Export a Premiere-native transcript
transcript_digest.py f.json [--json] [--min-conf 0.8] Transcript → sentence list with timecodes
import_transcript --item "name" --file f.json [--replace] Attach a transcript JSON to a clip in Premiere, verified by reading it back
export_otio --output f.otio Timeline structure as OpenTimelineIO (includes source media paths)
probe_api [--output dump.json] [--diff old.json] This Premiere build's UXP API, with a watch list of the calls the UXP port needs
export_frame --dir d [--at sec] [--width W --height H] One frame of the program output (default: the sequence's frame size)
export_frames --dir d --at 4,18.5,31.6 [--sheet] Several frames, optionally a labelled contact sheet
import_media --path f [...] [--bin "Name"] [--force] Import media into the root or a bin (created if missing); returns the new item names. Converts audio-only files Premiere cannot read (FLAC, Opus, Vorbis) to a 48 kHz 24-bit WAV beside them and imports that; refuses video in codecs it cannot read (VP9, AV1, Opus) with an ffmpeg command for each
insert_clip --item "name" [--at sec] [--vtrack N] [--atrack N] [--video-only] [--mode overwrite|insert] [--in sec --out sec] [--label "text"] Place a clip — or, with --in/--out, just that source range ("cut by sentence")
apply_plan plan.json [--dry-run] [--notes-as-markers] [--any-sequence] [--report r.json] Execute a JSON edit plan: imports, frame-snapped placements, free audio-track routing, conflict check, verification of both legs
set_scale --track V3 --at sec (--fit | --fill | --value %) Motion › Scale, fit/fill computed per clip from the media size
set_fps --item "name" --fps 59.94 [--dry-run] Interpret footage (frame-rate override; affects every sequence)
subclip --item "name" --in s --out s --name "New" [--bin "Bin"] [--dry-run] A subclip of a project item (source seconds snapped to its frames) in a bin, read back; a name already in that bin is refused
relink --item "name" --to "<folder>/new.mov" [--force] [--dry-run] Point a project item at another media file once ffprobe finds it matches the old media (duration, fps, streams); lists every use, read back
add_marker --at sec --name "…" [--comment] [--color red] Sequence marker
list_markers The active sequence's markers: guid, name, comments, start, duration, type, colour
remove_marker (--guid g | --name "…" [--at s]) [--dry-run] Remove one marker (a name several markers share needs --at or the guid); one undo step, read back
list_sequences Every sequence in the project: name, guid, active, and whether it is a bridge restore point
clone_sequence [--name "Seq"] [--as "New name"] [--keep 5] [--dry-run] Restore point before a risky edit: a tagged clone "<name> [restore YYYY-MM-DD HH:MM]" (not made active); the oldest restore points of that sequence beyond --keep are deleted
save_project Save the project and check that the project file's time stamp changed
remove_items --track V3 --from s --to s [--only-name "clip"] [--ripple --confirm-ripple] [--dry-run] Remove clips in a range (default: only those entirely inside)
split_clip --track V1 --at s [--render-check] [--dry-run] Split a clip without a razor (clone, trim, clone back; no clip is moved); both halves keep volume, scaling and effects
cut_range --from s --to s [--track V1 …] [--captions V3 --srt f.srt] [--render-check] [--dry-run] Remove a time range from every track and close the gap on all of them in sync, in one undo step for the ripple; optionally conform a caption movie
extend_cut --at s [--out s] [--in s] [--extend V1 …] [--render-check] [--dry-run] Open up a cut (breathing room) with more of the clips' own source, everything after it moving right on all tracks in one step
remove_project_items --bin "Bin" [--item "bin/name" …] [--dry-run] Remove bins and project items from the project in one undo step (media files stay on disk); refused while any sequence still uses an item
shift_items --from s --by s [--track V1 …] [--dry-run] Move everything at/after a time on all tracks (or the chosen ones) by whole frames in one undo step, keeping them in sync
snap_edges [--track V1 …] [--dry-run] Put clip edges that drifted microseconds off the frame grid back on it, so the picture cuts with the sound
conform_captions --track V3 --srt f.srt [--dry-run] Trim the pieces of a pre-rendered caption movie so no line flashes in at a cut
review_cuts [--track A1] [--render] [--words tr.json] Listening report: breathing room, clipped words, room-tone jumps and clicks at every cut, loudness with --render, plus the timeline lint
lint_timeline Slivers, cuts too close, gaps, overlaps, V/A cut mismatches, clips past the end, edges off the frame grid
export_audio --output f.wav [--render] The sequence's audio as a WAV (shadow mix of the sources, or Premiere's render)
list_presets [--format h264] Encoder presets (.epr) on disk, Premiere's own and yours, with the format each writes; no panel needed
export_video --preset NAME|PATH --output f.mp4 [--overwrite] [--dry-run] Render the active sequence with a preset inside Premiere, then check the file with ffprobe: frame size, fps and length against the sequence
export_aaf --output f.aaf [--embed-audio] [--audio-format wav|aiff] [--detach] / export_aaf --job ID The active sequence as an AAF, as a job with no time limit; the file must be new and not empty. Premiere may hold it with its modal "AAF export log" until someone clicks OK: the reply says so
export_xml --output f.xml [--overwrite] The active sequence as Final Cut Pro 7 XML (no dialog), checked for <?xml and <xmeml>
list_effects [--kind audio|video] Effects Premiere can add: video filters (match + display name), audio filters (display name, in the UI language)
add_audio_effect --track A1 (--at s | --from s --to s) --name "降噪" [--dry-run] Add an audio effect by its display name to the clip under a time: one undo step, verified; an unknown name lists the closest
list_effect_presets [--search text] [--file f.prfpset] Effect presets (.prfpset) on disk, Premiere's own and yours, with their effects, animation and how much apply_preset can replay; no panel needed
apply_preset --track V2 (--at s | --from s --to s) --preset "name" [--dry-run] Apply an effect preset through set_params, keeping its timing anchor; what UXP cannot write (masks, colours, Lumetri curves) is skipped and reported; verified
apply_ops --json ops.json --label "text" [--dry-run] Several whitelisted small writes in ONE transaction and one undo step: markers (add, rename, comment, colour, duration, type, move, remove), clip name / disabled (video), a static param value, sequence in / out and playhead, video track and bin names. Every target is resolved and every action built before anything commits; each op is read back
query --what clips|clip|markers|bins|sequence [--track V1] [--at s] [--from s --to s] [--bin path] Whitelisted reads by name: clips on tracks, one clip's components and params, markers, bins and items, the sequence's settings, in / out and player position

Everything prints a single JSON object; failures carry a code and an outcome: not_committed (nothing changed), committed (the write ran but did not do what was asked), partial (some steps of a multi-step command landed: see data.completed) or unknown (check the timeline before retrying). Writes accept --expect-seq "<name>" and refuse when another sequence is active. Where two project items share a name but not their media, address one as "<bin path>/<name>". insert_clip with a source range runs three labelled transactions (set range → place → clear range), each one step in Premiere's Undo history; the range is cleared even if placing fails.

Example: sentence-level cut, end to end

python transcribe.py footage.mp4 --output tr.json --language en
python transcript_digest.py tr.json
#  13. [00:52.8 - 00:56.4] Speaker 1: The second point is even more important ...
python ppro.py insert_clip --item "footage.mp4" --at 0 --in 52.8 --out 56.4 \
    --vtrack 1 --atrack 1 --label "sentence 13"
python ppro.py export_frame --dir ./frames --at 2.0   # eyeball the cut

The full workflow (coordinate math, take selection for raw footage, editing discipline, failure handling) ships pre-wired for both major agent ecosystems: Claude Code auto-loads .claude/skills/ppro-bridge/SKILL.md, and Codex (and anything else following the AGENTS.md convention) reads AGENTS.md. Open your agent in the repo root and it can run the loop autonomously; SKILL.md at the root is the master copy (the copies are generated, see "Versioning").

Python library

from ppro_bridge import ops, transport

rpc = transport.make_rpc(guard={"projectPrefix": "PAB_TEST"})  # every call pinned to a PAB_TEST* project
seq = ops.get_sequence(rpc)
clips = ops.list_items(rpc, track="V1")
ops.add_marker(rpc, at=4.0, name="Check this claim", color="red")

ops has one keyword-only function per CLI command, with the CLI's option names; each returns the command's data and raises BridgeError (code, outcome, data) on failure. rpc is always passed in, so nothing reaches a live Premiere by accident. The guard (project, projectPrefix, projectGuid, sequence, sequenceGuid) is checked by the panel on the project and sequence it is about to touch; a mismatch is refused with PROJECT_MISMATCH / SEQUENCE_MISMATCH before anything changes. fingerprint (get_sequence's) pins the timeline itself: the panel refuses a write that is not a dry run with PRECONDITION_FAILED once the timeline has changed.

MCP server

python -m ppro_bridge.mcp serves every CLI command as an MCP tool over stdio, on the official MCP Python SDK (optional: python -m pip install "mcp>=2.3,<3"). Register it with Claude Code (claude mcp add ppro-bridge -- python -m ppro_bridge.mcp, from the repository root) or Codex ([mcp_servers.ppro-bridge] in ~/.codex/config.toml). Commands that remove or ripple run as a dry run unless the call says confirm: true; the pairing token never appears in a reply. Details: SKILL.md, "MCP server".

Protocol and capabilities

  • Requests and replies follow ppro_bridge/actions.json (protocol 2): every action's params, reply keys, error codes and outcome rule. The panel, the fake panel and the CLI are unit-tested against it; the panel checks each request's params before running a handler (BAD_PARAMS, UNKNOWN_ACTION).
  • ping reports protocol, hostInfo (Premiere version, UI locale), modules, capabilities (per-build feature flags: Speech-to-Text, razor, MOGRT text, guids, …; each true, false or "unknown" until probed — ping --probe probes again), loadedAt and a codeHash of the loaded panel files. The CLI hashes its own plugin/ folder and warns when the loaded panel is stale.
  • Commands that need protocol 2 (set_params, replace_clip, transcripts) refuse an older panel with PANEL_OUTDATED instead of editing through it.
  • A host denylist in the panel (empty in releases) can mark a Premiere build as unsafe: the panel then answers ping only (with the reason) and refuses everything else with HOST_DENIED.

Listening, razor-free cuts and captions

UXP gives a panel no way to play or meter audio and, on 27.1, no razor. The bridge works around both without leaving Premiere's own edit model:

  • Listening — review_cuts rebuilds the dialogue track from the source files with ffmpeg (fast; volume not applied) or, with --render, has Premiere render its own mix through EncoderManager.exportSequence with Premiere's WAV preset (about a second per minute; adds integrated loudness and true peak). Every cut is measured on 10 ms windows against each clip's own noise floor: too little breathing room before or after the words (0.25 s / 0.35 s by default), a word cut off, room tone jumping, a click. The same report lints the timeline for slivers, near-miss V/A cuts and gaps. review.pad_cut_points turns word timings into cut ranges with breathing room that never reach into a neighbouring word.
  • Cutting — split_clip and cut_range clone the clip (Premiere's track-item clone keeps every attribute), trim the pieces and clone the right piece into place; the ripple after a cut is one transaction across all chosen tracks, so tracks with gaps stay in sync (Premiere's own ripple works per track). extend_cut does the opposite — it opens a cut up with more of the clips' own source for breathing room. Every step is read back; dry runs plan the exact steps without writing. No command moves a clip past another one on its track: on Premiere 27.1 such a move renders the clip silent or blank while every read looks right (B1, below), so trim_clip refuses it. --render-check proves an edit by render: one audio render of the sequence, each audio piece compared with its own source's waveform (music over a silent piece cannot hide it), and a frame in the middle of each video piece.
  • Captions — for a pre-rendered caption movie cut along with the A-roll, conform_captions reads the SRT it was rendered from and trims pieces that would flash a line for a few frames at a cut.
  • Volume — Volume › Level is linear in UXP (dB = 20·log10(level) + 15); set_volume speaks dB. Track volume and pan are out of UXP's reach; clip audio effects are added by their display name (add_audio_effect).

Transcription notes

  • Whisper runs entirely on your machine: weights download once from Hugging Face, audio never leaves the host. CUDA is auto-detected; on CPU prefer --model medium.
  • Whisper's word confidence values are real — --min-conf on the digest is a usable quality filter. Premiere-native transcripts export placeholder 1.0 confidence; judge those by reading the text.
  • Transcript times are source-clip seconds; timeline positions are sequence seconds. The OTIO export's source ranges convert between them: sequence_time = clip_position + (source_time − source_range_start).
  • Premiere Speech-to-Text (transcribe_clip, 26.5+) runs Premiere's own transcription on one clip. It returns at once with a job id; the CLI polls list_transcripts --jobs every 5 s (up to --wait seconds) and exports the result. Languages are Premiere's codes such as en-US — transcript JSON uses lowercase codes such as en-us. A missing language pack is refused (LANGUAGE_UNAVAILABLE) rather than triggering Premiere's install prompt. Jobs live in the panel: a panel reload forgets them (TRANSCRIBE_LOST; the transcript may still land — check list_transcripts).
  • import_transcript validates the file against the transcript schema, imports it, reads it back and compares word counts (verified working on 27.1 build 17). Some older builds (26.x) were reported to import an empty shell; the bridge then reports TRANSCRIPT_NOT_IMPORTED (outcome committed) — import the file by hand in Premiere's Text panel instead. A clip that already has words needs --replace.

Versioning

VERSION is the only place the version is written by hand. python scripts/sync_version.py copies it into the panel manifest, broker, fake panel, package and generated panel spec, sets the headings of SKILL.md and this README, and regenerates AGENTS.md and the two skill copies from SKILL.md. --check writes nothing and lists every drift (and a missing changelog entry); a unit test runs it. Edit SKILL.md, then run the script (--only docs regenerates just the copies).

Live smoke test (contributors)

tests/live/live_smoke.py exercises the real panel in a Premiere test project. It refuses to run unless the open project's name starts with PAB_TEST and pins every request to that project and its active sequence by guid, so if someone switches projects mid-run the panel refuses instead of writing elsewhere.

python tests/live/live_smoke.py --media <test media folder>            # read-only checks
python tests/live/live_smoke.py --media <test media folder> --write    # plus writes

The read phase checks ping, capabilities, guards and every read action's reply against the spec. With --write it first copies the saved .prproj into its report folder, imports the test media into a new bin PAB_SMOKE_<run id>, works only in a scratch region after the end of the active sequence (top video track, a free audio track) — inserts, ranges, scale, effect params in all three time bases, a transition, replace_clip, a marker, transcript import, Speech-to-Text, a subclip placed whole and a relink round trip — then removes exactly the clips it placed (never ripple), removes its PAB_SMOKE_* bins with remove_project_items (after a dry run that finds them unused in every sequence) and compares the timeline and project with how it found them. Its markers stay (the report lists them); --keep leaves the clips and the bins too. Each check prints PASS, FAIL, SKIP or KNOWN_ISSUE (e.g. Premiere's empty transcript import); any FAIL exits 1. tests/live/make_media.py <folder> generates the media it expects with ffmpeg (broll_audio.mp4, EN/title.mov, CN/title.mov, optionally speech_en.wav). The unit suite rehearses the harness against the fake panel.

tests/live/api_smoke.py --media <test media folder> is the API check that comes before porting more editing commands: it saves probe_api's inventory of the build and runs the panel's api_smoke cases (removing markers, transitions, effects and keyframes, cloning and activating sequences, audio effects by display name, bins, relinking, custom properties, sequence settings, FCP XML export, AAF export with --aaf (Premiere waits on a modal log), MOGRT insertion with --mogrt, ...) on a scratch clip in a PAB_TEST* project. Each step reports pass, fail, absent, error or skipped; each case puts back what it changed and lists anything it could not under leftovers. It also times a video export and export_frame, checks a phone clip for black frames (--phone-media) and, with --watch-tracks, times how long get_sequence takes to see a track you add. Reports: report.json, api_smoke.md and probe_api.json in --out. Only a dev build of the panel registers api_smoke; this repository and its release zips are release builds (plugin/lib/build.js says dev: false, ping reports build.dev: false), and the runner stops at once and says so. To run it, set that flag to true in your copy and Reload the panel in UXP Developer Tool.

Security model

  • The broker binds the loopback addresses 127.0.0.1 and ::1 only (the panel's ws://localhost arrives over ::1 on Windows). No remote access, by design — do not port-forward it.
  • The broker accepts only two kinds of WebSocket handshake: none with an Origin header (the CLI) and Origin: file:// (the UXP panel). Browsers let any page open a socket to localhost, but always send an Origin — the page's http(s) origin, or null from sandboxed frames and data URLs — and are refused with 403.
  • Pairing (0.7). The broker, its controllers (the CLI, scripts, the MCP server) and the Premiere panel all read the pairing token from %USERPROFILE%\.ppro-agent-bridge\token, and none of them sends it: each side proves it knows the token with an HMAC-SHA256 challenge-response over both sides' random nonces, the client first, and the client sends its request (the CLI) or runs one (the panel) only after the broker has proved the token back. A program that holds port 8017 while no broker runs learns neither the token nor a request, and the panel runs nothing it sends (the panel's status line reads "Not paired: ..."); a program that connects to the broker without the token is refused (AUTH_FAILED, close 1008; a panel: close 4401) and learns nothing but the challenge. That keeps out local programs and other Windows users that cannot read your profile. The broker creates the token file (and the folder) with an ACL of their own: you and SYSTEM. A 0.7 CLI or panel needs a 0.7 broker (BROKER_UNVERIFIED otherwise); the broker still takes the token itself from a 0.5 / 0.6 CLI. A 0.6 panel cannot pair and is refused, unless you set PPRO_PANEL_AUTH=legacy (or {"panelAuth": "legacy"} in settings.json) while you update it — that lets any local program register as the panel again. The panel cannot read environment variables: with PPRO_BRIDGE_TOKEN set, it pairs only when the token file holds the same token.
  • A connection must finish its hello within 10 s (at most 64 at once), a panel hello naming another plugin id is refused (close 4403), and a registered panel is never replaced while its socket is open — answering or busy, a newcomer is refused (4409). The panel also refuses network (UNC) and device paths in every param it hands Premiere (the spec's localPath), and its exports keep their file type (the spec's extensions: no .bat from export_otio). The CLI goes further: it follows the symbolic links and junctions in a path by reading them (never opening their targets) and refuses a path that leads to a share that way, and it checks every file it opens or writes itself (plans, transcripts, SRT, JSON params, its own .json outputs) by the same rules. The broker does not start beside another program on its port: when 127.0.0.1:8017 or [::1]:8017 (where the panel arrives first) is taken it exits and names the holder ("It is held by PID n (name)").
  • The audit log records every request (action, shortened params, guard, outcome, queue and run time, the sequence and track counts Premiere reported), every refusal and every panel connection, so a session can be reconstructed afterwards.
  • One request reaches Premiere at a time (concurrent UXP calls can crash it). A request whose outcome is uncertain keeps the queue closed until the panel answers, and a handler that never returns marks the panel stuck instead of piling on.
  • The panel executes a fixed whitelist and checks every request against the action spec first (unknown actions and params are refused); there is intentionally no eval / run-arbitrary-script command. New capability = a new named handler in plugin/lib/, plus its entry in actions.json, reviewable in a diff.
  • Requests can be pinned to a project and sequence (name, prefix or guid); the live smoke test uses this so it can only ever write into PAB_TEST* projects.
  • The panel manifest allows the network only to ws://localhost/ (UXP takes no IP address or port in that list; the panel dials ws://localhost:8017), may launch only .bat and .pyw files (the Start bridge button; UXP asks the user first) and open https addresses in the browser — and the panel opens only this repository's release pages and its new-issue page (plugin/lib/support.js allowedUrl; UXP asks first) —, and asks for localFileSystem: fullAccess. It asks for no clipboard access (UXP offers only read-and-write): "Copy a problem report" shows the whole report in a box, selected for Ctrl+C. The panel itself reads only its own plugin folder (code hash, launcher.json), the pairing token file, and lists the folder that should hold start_bridge.bat; the media, frames, OTIO, renders and presets it is given are opened by Premiere's own API calls with the paths in the request. Whether those calls keep working under a lower level (plugin) is not measured, so fullAccess stays until a live test shows it can go.
  • Every write operation lands in Premiere's Undo history under a caller-supplied label — the human always has a one-keystroke rollback.
  • Transcription is local; nothing is uploaded anywhere (Premiere's own Speech-to-Text runs inside Premiere, under Adobe's terms).
  • The one outbound request: the update check. Only the broker goes online, at most once a day (an attempt counts, so restarts do not ask again), in a background thread: one HTTPS GET of GitHub's releases API for this repository (https://api.github.com/repos/…/releases/latest), with the User-Agent ppro-agent-bridge/<version>, no cookies and nothing that identifies the machine, the user or a project; redirects only to the same host, 5 s timeout. It keeps the release's version, date and page URL — the URL only when it is one of this repository's release pages — in %USERPROFILE%\.ppro-agent-bridge\update.json; the release text is never kept or shown. ping, the panel ("0.7.1 available · View · Skip this version"), doctor and the setup window show the result. A failure is quiet (one audit record). Turn it off with PPRO_UPDATE_CHECK=0 in the broker's environment, or {"updateCheck": false} in %USERPROFILE%\.ppro-agent-bridge\settings.json (where "Skip this version" keeps "skipVersion"); then nothing is sent.
  • The problem reports — python -m ppro_bridge.report and the panel's "Copy a problem report" — are built from a whitelist: versions, Premiere's version, capabilities, check results, the update check's version, and recent error codes. Never the token, project or sequence names, paths, error messages or request params; the panel shows the whole text before it is copied.
  • Treat any agent bridge as shell-equivalent access to the host app. Run it on test projects or copies first.

Known limitations

  • Same-named project items with different media must be addressed by bin path ("CN/title.mov"); the bridge refuses to guess.
  • No speaker diarization in the Whisper path (single speaker stamped).
  • Speed-remapped clips are deliberately unsupported (v1 fails loudly rather than drifting silently); keyframes on speed-changed clips only in media time.
  • Some older Premiere builds (26.x) were reported to import transcripts as an empty shell (TRANSCRIPT_NOT_IMPORTED; 27.1 imports correctly), and importing over an existing transcript was reported to delete it. Import by hand in the Text panel when that happens.
  • Premiere 27.1.0 (builds 17 and 21) has no native razor (capabilities.razor is false); split_clip / cut_range split by clone + trim instead (more undo steps than a razor cut). Markers do not move with cut_range.
  • Timelines cut with split_clip / cut_range of bridge 0.4.0 to 0.5.1 can hold pieces that Premiere 27.1 renders silent or blank although every read looks right (B1, a clip moved past another on its track; fixed in 0.6.0). Find them with review_cuts --render and export_frames; SKILL.md "Cutting without a razor" says how to repair them.
  • The listening report's shadow mix ignores clip/track volume, effects and other tracks (--render hears Premiere's real mix). It hears sound, not meaning: a breath counts as sound, and picture problems show only in the lint. It also needs each clip's media file: Enhance Speech results and other generated items have no media path, so the shadow mix refuses with NO_MEDIA — use --render there.
  • Speech-to-Text jobs are tracked by the panel and lost when it reloads.
  • UXP cannot add tracks, change speed, link/unlink clips or create captions; the bridge refuses rather than working around these silently. Audio transitions are not reachable at all, and neither is Enhance Audio (27.1): select_items selects the clips, the user flips its toggle in the Properties panel once.
  • Frame export inherits a known Adobe double-extension quirk; the CLI resolves the real filename automatically (resolvedPath).
  • Placed media is not auto-scaled: a 4K clip in an HD-wide sequence lands as a 2× crop. Use set_scale --fit/--fill (or "scale" in a plan).
  • Premiere 27.1 rejects importFiles without a real target bin; the bridge passes the root bin (or the named --bin).
  • The .ccx package is built and checked offline only: Creative Cloud's installer on the test machine knows Premiere 26.5.2 but not the 27.1 Beta, so no install has been tried yet.
  • Windows only; tested on Premiere 27.1 on one machine. Expect edges.

Changelog

  • 0.7.1 — Switching projects: open_project --path x.prproj opens a project, or brings one that is already open to the front, so the next commands work on it: no dialogs, nothing saved or closed, confirmed with get_project. An open project is not reloaded (live on 27.1 build 41: a sequence that existed only in memory was still there after switching away and back). Refused while an export runs. The CLI reads the file first and refuses a project that points at a network path, because opening it could offer your Windows credentials to that computer (open such a project yourself in Premiere), and a file that is not a Premiere project, because Premiere would stop on an error window. remove_transition took off only some transitions: on 27.1 a transition added over UXP is not among the track's items, so the panel counted 0 and refused. It now goes by the OTIO export, makes the call anyway and proves the result there (head, tail and across a cut from either side, live on build 41).
  • 0.7.0 — Security, the release gate: a review with an attack simulation on this machine (two red teams, two blue teams, a release-process reviewer, and QA re-running every attack); SECURITY_REVIEW.md has the threat model, the 43 findings and what was done about each. The broker, its clients and the panel now prove the pairing token to each other with an HMAC-SHA256 challenge-response and never send it: a program holding port 8017 learns neither the token nor a request, and the panel runs nothing it sends (live: a fake broker on 8017 got nothing). Upgrade the broker and the panel together: a 0.6 panel is refused (PPRO_PANEL_AUTH=legacy lets it in, with a warning) and a 0.7 CLI or panel needs a 0.7 broker (BROKER_UNVERIFIED); 0.5 and 0.6 CLIs still work. Every path a command reads or writes goes through one check that also follows links and junctions, so a local-looking path into a network share is refused like \\host\share. UXP Developer Tool runs only while the bridge loads the panel and is closed again: its local service takes a connection from any web page, and the panel keeps running without it (live: open 9.3 s). The FLAC→WAV conversion, the start scripts, the Start bridge button's launcher.json, the setup window's commands, the update check, MCP redaction, the problem report and the token file's ACL were hardened, and the release workflow pins every action to a commit SHA. Installer: PremiereAgentBridge-Setup-<version>.exe (Inno Setup, English and Simplified Chinese wizard): a per-user install without admin rights, with its own Python (python.org's embeddable 3.14.10, pip 26.3 and pinned wheels, every file checked by SHA-256 when it is built, always run isolated with -I), Start menu and optional desktop shortcuts, optional start at login and an uninstaller. Built, installed, run and uninstalled on Windows 11, and the whole test suite passes under the bundled Python. It is not code-signed yet, so SmartScreen warns (More info → Run anyway); it takes over a zip install's shortcuts of the same name and its uninstaller removes them (setup_bridge.bat puts them back). MCP server: python -m ppro_bridge.mcp serves every CLI command as an MCP tool over stdio, on the official MCP Python SDK (mcp, optional: the CLI does not need it). The tools are generated from the CLI itself; 35 common ones are listed, the rest are found through find_tool / describe_tool / run_tool. Commands that remove or ripple run as a dry run unless the call says confirm: true; one broker connection per server makes a call cost milliseconds; the pairing token is scrubbed from every reply. Generic operations: apply_ops runs whitelisted small edits (markers, clip name and disabled state, static param values, sequence in/out and playhead, track and bin names) in one transaction with one undo step, each read back (live: one Ctrl+Z undid 11 of them); query reads the same things. Faster CLI: about twice as fast to start (live on 27.1: ping 0.25–0.32 s instead of 0.55 s; a standard-library WebSocket client, commands loaded on use). Update notice: the broker asks GitHub at most once a day whether a newer release is out (one HTTPS request, nothing about the machine or projects sent; off with PPRO_UPDATE_CHECK=0 or settings.json); the panel shows "0.7.1 available · View · Skip this version", and ping, doctor, the setup window and the problem report say it too. Help: the panel's "Copy a problem report" (the report in a box: click it, then Ctrl+C) and a GitHub Issues link; python -m ppro_bridge.report prints the same report (never the token, project names or media paths). Exports: export_aaf (an export job: Premiere 27.1 stops on a modal "AAF export log" when an effect or transition cannot go into the AAF; the panel keeps answering and the reply asks the user to click OK — live) and export_xml (Final Cut Pro 7 XML, no dialog — live). Fixes from real jobs: transitions follow a ripple. On Premiere 27.1 a move dropped the moved clips' video transitions (confirmed live). shift_items, cut_range, extend_cut and split_clip now read them from OTIO before their first write, put back each one that went (one undo step each) and check them with a second export (transitions, transitionsVerified, a warning when one is not back). At a cut with a neighbour, Premiere centres an added transition even when asked for a one-sided one and drops each half that has no media under it, so a one-sided fade there is added centred at twice its length when the clip itself has no media on the side Premiere drops (before its in point for a fade-in) and the neighbour has the fade's length of media on the side that stays; otherwise it is reported missing with how to add it by hand (live). set_active_sequence confirms the switch before it answers (a quick switch away and back once reported success while Premiere stayed put). set_scale --fit/--fill and apply_plan's "scale" read a clip's rotation, so a Sony or phone clip stored 3840×2160 with a 90° rotation fits and fills as the vertical picture Premiere shows (live). import_media and apply_plan convert audio-only files Premiere cannot read (FLAC — a music download got a modal dialog and hung the panel on 27.1 —, Opus, Vorbis) to a 48 kHz 24-bit WAV beside them and import that instead, reusing a WAV of the same length (live). From the first real jobs on 0.7 (7-8 Nov 2026): one failed tasklist used to put the broker back in its old way, UXP Developer Tool started, kept and restarted (seen once in the audit log); now a list that cannot be read starts nothing until it can. remove_items --only-name refuses a removal that would take anything else (a removal by track number had just deleted a graphic the editor had moved there). insert_clip --video-only and a plan's video_only place the picture without its sound, clone_sequence --as makes a plain named copy of a sequence for another version of the cut, set_params --dry-run checks a request without changing anything, and add_audio_effect --from/--to adds an effect to every clip in a range. A network path as a preset is refused before the ffmpeg check, and the test suite no longer depends on ffmpeg or on how TEMP is written.
  • 0.6.0 — An icon: an L-cut (the picture cutting before the sound) under an orange playhead on a deep purple tile (ppro_bridge/bridge.svg, bridge.ico with every Windows size drawn on its own pixel grid by scripts/make_icon.py). The setup's shortcut now carries it and goes in the Start menu too; the setup window shows it. New since 0.5.1: set_active_sequence opens another sequence for the next commands; set_params sets keyframe interpolation ("interpolation", on new or existing keys). New remove_project_items command: bins and project items leave the project in one undo step, refused (IN_USE) while any sequence in the project still uses one, read back afterwards. New shift_items command: everything at/after a time on every track (or the chosen ones) moves by whole frames in one undo step, with a dry run and a read-back, for room where extend_cut finds no edit point (it now says so). Fixes from real jobs: import_media and apply_plan refuse files Premiere cannot import (VP9, AV1, Opus — YouTube downloads hung the panel) before sending anything, with an ffmpeg command for each (--force sends them anyway); apply_plan and replace_clip read the video stream's own length, so a range past the end of a picture that stops before its audio ends on the last frame (pictureEnds) instead of landing frames short. Changed behaviour: set_volume and set_scale with --from/--to now take only the clips entirely inside the range (like remove_items); a neighbour that only touches it — ending where the range starts, or starting where it ends — is left alone and listed in skipped. --mode overlap gives the 0.5 behaviour. Hardening: get_sequence returns a timeline fingerprint, and every write command takes --expect-fingerprint — the write is refused (PRECONDITION_FAILED, nothing written) if the timeline changed since it was read; paths Premiere would open are checked as typed, by the CLI and by the panel itself (no UNC shares, which would offer the user's Windows credentials to that host, and no Windows device paths, with no override: map a drive letter for a NAS; \\?\<drive>:\ long paths are fine; no NTFS data streams; exports keep .png / .wav / .otio / .epr); the broker refuses writes beyond 300 a minute (WRITE_LIMIT, a runaway-script guard; real jobs peaked at 118) and can space writes out (PPRO_WRITE_GAP_MS, off by default). New panel commands (phase 2): list_markers / remove_marker (by guid, or by name with --at); list_sequences and clone_sequence, a restore point before a risky edit (renamed " [restore YYYY-MM-DD HH:MM]", tagged, not made active; --keep 5 deletes only the oldest untouched restore points of that sequence); save_project (checks the file's time stamp); remove_transition (finds the transition in an OTIO export, names its owner, verifies nothing else went), remove_effect (Motion, Opacity, Time Remapping and the audio volume refused) and remove_keyframes (a range or all, --keep-value), so extend_cut's KEYFRAMED refusal now hands over the exact remove_keyframes command for each held key; subclip (snapped to the item's frames, read back) and relink (refused unless ffprobe finds the new file matches the old media; lists every use in every sequence); list_presets (Premiere's and the user's .epr presets, format decoded) and export_video (renders through Premiere and checks size, frame rate and length with ffprobe); list_effects and add_audio_effect (by the localised display name, e.g. "降噪"); select_items selects clips in the Timeline for what only Premiere's panels do — Enhance Audio cannot be added over UXP, but its Properties toggle acts on every selected clip. Every write has a dry run, refuses before changing anything, is one labelled undo step where Premiere allows, and is read back. Panel hardening: a track or clip list that cannot be read is an error, not an empty timeline; a fingerprint guard key the panel checks before any write (transport.make_rpc(guard={"fingerprint": fp}); the same hash as get_sequence's, pure-JS SHA-256); a 1000-action cap per undo step; shift_items checks room per track in the panel too (ping features), so a left shift past a music bed is one step. The live smoke runs remove their own test bins. Found in the live test: set_params sets a static value on an audio-effect param Premiere left animated without keys (switching the animation off in the same step); a subclip's range cannot be read back on 27.1 (rangeVerified: false); a clip list read right after a removal can hold a null for a moment, so the panel reads it again. Phase 2.5, ideas from other UXP plugins (khanjar, Kaltura, CorridorKey), each verified on Premiere 27.1:
    • Connection. The panel connects to ws://localhost:8017: the manifest's network rule is ws://localhost/, because UXP accepts no IP address or port there. The broker listens on 127.0.0.1 and ::1, and on Windows the panel arrives over ::1. The hello carries the plugin id. A second panel is refused (4409) while the first is connected. The panel's callbacks run inside an error guard. The broker does not start when another program holds [::1]:8017: the panel would reach that program first (found in the release review; only a machine without IPv6 gets a warning and 127.0.0.1 alone).
    • Start bridge. With the broker down, the panel shows a Start bridge button that opens start_bridge.bat; UXP asks once for consent. The broker's loader then leaves a panel that already runs the same code alone, and starts UXP Developer Tool detached from the broker's console, so closing the broker window no longer closes it.
    • Panel header. The panel shows the bridge's icon above its log, in the website's black-and-hairline look: the icon is in full colour while the broker is connected and faded while it is not, so you can tell at a glance whether the bridge is running. Under it, a first run gets two steps: open Claude Code or Codex in a folder for the job, then paste a ready-made first message that names where this bridge is installed. The panel speaks Chinese in a Chinese Premiere, English otherwise.
    • Effects on graphics. set_params inserts new effects on a graphic before its text layer; appended after it they did nothing. It waits for a new title's layers, which arrive about a second after placement. The reply's createdComponents gives each new effect's position.
    • Export as a job. export_video renders with no fixed time limit (--detach, --job), and a second render while one runs is refused (EXPORT_RUNNING). 27.1 fires no encoder events, so a job ends when Premiere's export call returns. The stall check counts the temporary files an H.264 render writes.
    • Effect presets. list_effect_presets / apply_preset apply Premiere's .prfpset effect presets (factory, Lumetri and your own) through set_params, keeping their timing anchors. An added effect's param names are checked on the clip before anything is written. Masks, colours and Lumetri curves are skipped and reported.
    • Packaging. scripts/build_ccx.py builds a .ccx package (a release build, which leaves out the api_smoke test action; the release zip is one too, its plugin/lib/build.js saying dev: false), and python -m ppro_bridge.doctor adds a read-only check of the installer. The installer on the test machine knows Premiere 26.5.2 but not the 27.1 Beta, so installing the package is not tested yet. ping reports build: {dev, packaged}. Fixed (B1): on Premiere 27.1 a clip moved past another clip on its track (createMoveAction) renders silent (−180 dB) or blank (0 opaque pixels), and once the clip it passed over went silent too, although list_items and inspect_items read right. split_clip (and every split in cut_range) used that move for its last step since 0.4.0; it now clones the parked copy straight to the cut and removes the parked copy (5 undo steps instead of 4), measured clean by render on 3 Nov 2026. trim_clip moveTo refuses a path that crosses or lands on another clip (PRECONDITION_FAILED), and cut_range lost its per-clip move fallback for panels without shift_items. Timelines cut with split_clip / cut_range before can hold such pieces: find them with review_cuts --render and export_frames; re-place a silent audio piece with insert_clip of the same source range (a nudge does not bring it back), nudge a blank video piece a frame and back. New --render-check on split_clip, cut_range and extend_cut (RENDER_CHECK_FAILED when a piece does not play).
  • 0.5.1 — The setup window (setup_bridge.bat) now matches the project page: pure black, black and white, a thin glass rim of light on its panels, serif step titles and white main buttons. The blurred background haze is gone (it banded on near-black), and so are the grey halos around cards. Green, amber and red still mark ready, optional and needs setup, always with an icon and a word. Nothing else changed.
  • 0.5.0 — Hardening, quick deploy and groundwork for a larger UXP port. Fixes from real jobs after 0.4.0: the shadow mix refuses clips without a media file (NO_MEDIA: Enhance Speech results and other generated items used to mix as silence). From a real language-version swap: replace_clip attributes transitions by owner and side, so a neighbour's outgoing-only fade at the clip's start is no longer taken as the clip's own (it is put back exactly when the removal takes it), refuses off-centre or handle-less shared transitions and components the new clip cannot get (Premiere 27's colour-tool container, or anything the panel's new set_params dry run cannot create; --skip-effect leaves one behind) before writing, and snaps an explicit --in/--out to the nearest source frame; add_transition refuses an edit point that already has a transition (TRANSITION_EXISTS: Premiere keeps the old one) and reads the result back from OTIO. From a festival-video re-cut: extend_cut lists keyframed Level/Opacity on the clips it lengthens (heldKeyframes) and refuses an extension that a fade's keyframe would hold silent or invisible (KEYFRAMED; --allow-silent); dry runs of remove_items, set_scale and set_volume honour --expect-seq; export_frame(s) flag all-black PNGs (black, with a warning), report the PNG's size and time the export (exportSeconds, waitedSeconds), and poll for the file every 50 ms instead of 500 ms. Hardening: a pairing token (ppro_bridge/auth.py, created by the broker in %USERPROFILE%\.ppro-agent-bridge\token, sent by the CLI by itself; others get AUTH_FAILED) and a JSONL audit log of every request, refusal and panel connection (ppro_bridge/audit.py, 30 days kept). Groundwork for porting a large CEP tool catalogue to UXP: probe_api (panel action and CLI) lists every class, method and instance member of the build's UXP API and checks a watch list; the panel's api_smoke test cases with tests/live/api_smoke.py find out, in a PAB_TEST* project, which documented calls really work. On Premiere 27.1 (build 21) almost every call it tried does — removing markers, transitions, effects and keyframes, cloning and deleting sequences, subclips, relinking, FCP XML export; a sequence's frame size did not change through createSetSettingsAction, and AAF export waits on a modal log (opt-in: --aaf). Quick deploy: setup_bridge.bat opens a setup window (ppro_bridge/setup_ui.py, Edge app mode) that checks the machine step by step and runs the fixes from buttons (--console: the text doctor check, with y/n fixes), and start_bridge.bat runs the broker with --load-panel, which starts UXP Developer Tool minimized and loads the panel whenever Premiere connects to it, so the panel survives Premiere restarts without clicking Load. Breaking: a 0.4 CLI cannot talk to a 0.5 broker (it sends no token).
  • 0.4.0 — Foundations. The CLI is now the ppro_bridge package (python -m ppro_bridge; cli/ppro.py and the other scripts keep working as wrappers), with one function per command in ppro_bridge.ops. One action spec (ppro_bridge/actions.json, protocol 2) that the panel, the fake panel and the CLI are tested against: the panel checks params before any handler runs, every error has a code, outcomes add committed and partial, and requests can carry a project/sequence guard (name, prefix or guid). ping reports protocol, host info, per-build capabilities (--probe), a code hash (the CLI warns when the loaded panel is stale) and a host denylist (ping-only mode, HOST_DENIED); commands that need the new panel refuse an old one (PANEL_OUTDATED). The panel is split into modules and builds all of a transaction's actions before adding any. New: transcribe_clip (Premiere Speech-to-Text, 26.5+), set_params and add_transition on the CLI, with an explicit keyframe time base (media, clip or sequence) and occurrence for repeated effects, the whole request validated first. import_transcript validates the file, verifies the import by reading it back (TRANSCRIPT_NOT_IMPORTED for Premiere's empty-import bug) and needs --replace to overwrite; export_transcript takes bin paths. replace_clip refuses keyframes that would fall outside the new range (--drop-outside-keyframes) and reports partial when it stops after the removal. transcribe.py writes valid Adobe language codes and refuses silence. One VERSION file (scripts/sync_version.py), a fake panel that answers all 25 actions, panel tests on Node, and a live smoke test that only writes into PAB_TEST* projects. Also new, from a real re-cut job: a listening report (review_cuts, lint_timeline, export_audio: shadow mix or Premiere's own render via the new export_sequence action, breathing room / clipped words / room-tone jumps / clicks per cut, slivers, loudness), razor-free editing (split_clip, cut_range with a one-step, sync-keeping ripple, extend_cut for breathing room; panel actions clone_clip, trim_clip, shift_items) that keeps every clip attribute, conform_captions for pre-rendered caption movies, set_volume in dB, and set_params / inspect_items on audio tracks. Sequence positions, ends, moves and clone offsets land on exact frame ticks (a microsecond past a frame boundary showed a trimmed clip one frame longer); lint_timeline flags edges that drifted off the grid (OFF_GRID) and snap_edges puts them back. Breaking for scripts: the panel refuses unknown or mistyped params (BAD_PARAMS); set_params keyframes need a timeBase; export_transcript refuses empty placeholder transcripts (NO_TRANSCRIPT) and ambiguous names; import_transcript refuses to overwrite without --replace; CLI output no longer carries the broker's id/broker keys, error messages start with <action>: , and import_media lists missing files under data.missing; failed reads report not_committed instead of unknown; the CLI, transcribe.py and transcript_digest.py write UTF-8 to stdout/stderr; the digest attaches punctuation tokens and honours their sentence ends, so sentence counts can change; transcripts written by 0.3.x transcribe.py (zh-cn, se-se, …) are upgraded on import with a warning. cli/ppro.py keeps its 0.3.3 names (rpc, call, BROKER_URL, cmd_*, …), and assigning them still redirects the CLI.
  • 0.3.3 — Safety release. Fixes the cause of "one frame short" placements: Premiere floors source in/out points to whole frames, and a frame boundary passed as float seconds (1.001 → 1.000999…) landed a tick short and lost its frame; the panel now nudges every in/out/position by 1 µs and apply_plan sends exact, unrounded times (measured on 27.1). The broker only accepts the CLI and the panel (the web-page check also refuses null and localhost pages now), sends one request at a time, reports outcome on every failure and its own version in ping. The panel runs requests in order with a watchdog. replace_clip refuses same-named items with different media, sends new audio to a free track instead of the last one, moves keyframes with the in point and verifies them, and refuses speed-changed clips. apply_plan refuses or stops when the active sequence isn't the plan's, places whole-file ranges as whole clips without overrunning the next clip, and verifies the audio leg too, to half a frame. insert_clip clears its source range even when placing fails and refuses missing tracks; set_fps refuses ambiguous names and has --dry-run; remove_items --ripple needs --confirm-ripple and reports what moved. Manifest minimum is now 26.3. Added a unit test suite.
  • 0.3.2 — replace_clip (CLI, cli/replace.py) on new panel actions set_params (values/keyframes, re-creates filters by match name), add_transition, list_transition_names. Proven on a language-version swap of 11 edited clips: before/after effect + transition reports identical. Frame-accurate checks (tolerance < 1 frame) caught and fixed interpretation (60 vs 59.94), end-of-media clamping and transition tick rounding. The broker now refuses connections from web pages (browser Origin check); needs websockets>=14. Added LICENSE, requirements.txt, .gitignore/.gitattributes.
  • 0.3.1 — inspect_items: read-only effect chains (Motion, Opacity, filters, params, keyframes) so an agent can see an editor's changes before touching clips.
  • 0.3.0 — Learned from a real B-roll/graphics job on a vertical 59.94 sequence. get_sequence reports frame size, fps, duration and tracks; new list_items, list_project_items, export_frames (contact sheets), apply_plan (JSON edit plans with dry-run, frame snapping, audio routing, conflict check, verification), set_scale, set_fps, add_marker, remove_items; import_media --bin and newItems; frame export defaults to the sequence size; version handshake in ping; fixed import_media on Premiere 27.1; stateful fake panel; port override for headless tests.
  • 0.2 — Local Whisper transcription (transcribe.py), sentence digest, import_transcript, sentence-level insert_clip --in/--out, AGENTS.md for Codex.

Roadmap

  • Next: code signing through SignPath Foundation (installer/SIGNING.md); the installer putting back a zip install's shortcuts when it is uninstalled (QA-9 in SECURITY_REVIEW.md); the panel refusing to overwrite an existing export by itself (DOC-9, today the CLI checks it).
  • Hardening still to do: localFileSystem lowered from fullAccess once a live test shows Premiere's import and export calls work without it; installing the .ccx on a Premiere that Creative Cloud's installer knows.
  • Later: native razor once Premiere exposes it (fewer undo steps than clone + trim), range placement as one undo step via subclips, unique-ID clip addressing, sequence-level transcripts and SRT/caption export, a speaker diarization option in the Whisper path, macOS validation.

Acknowledgments

API usage patterns were verified against Adobe's official uxp-premiere-pro-samples. The setup window's animated icons come from Material Line Icons by Vjacheslav Trushkin and SVG Spinners by Utkarsh Verma, both MIT-licensed and found via movin.design/icons; see THIRD_PARTY_NOTICES.md. This project is not affiliated with or endorsed by Adobe, OpenAI, or Anthropic.

License

MIT — see LICENSE.

About

Transcript-driven, closed-loop AI editing bridge for Adobe Premiere Pro, built on official UXP APIs

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages