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.6.0). Tested on Premiere Pro 27.1 / Windows 11
(earlier releases also ran on 26.x). macOS is not supported yet.
Forty-two 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
- Download
ppro-agent-bridge-v0.6.0.zip from
Releases, right-click it and choose
Extract All.
- 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.
- 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,右键「全部解压缩」。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
-
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.
-
Every day: double-click start_bridge.bat (keep its window open). It runs
the broker with --load-panel: UXP Developer Tool is started minimized when it
is not running, and the panel is loaded into Premiere whenever Premiere
connects to it — at start-up, after a Premiere restart, after UXP Developer Tool
restarts. No clicking Load. 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.
-
Smoke test (with any project open):
cd cli
python ppro.py ping
python ppro.py get_project
Pairing is automatic: on its first start the broker writes a random token to
%USERPROFILE%\.ppro-agent-bridge\token, and the CLI (and any script using
ppro_bridge) sends it on every connection. (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 both sides. 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 |
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 | 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 |
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.
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.
- Controllers (the CLI, scripts, later the MCP server) must send the pairing token
from
%USERPROFILE%\.ppro-agent-bridge\token, compared in constant time; a
missing or wrong one gets AUTH_FAILED and the connection is closed before
anything reaches Premiere. That keeps out local programs and other Windows users
that cannot read your profile.
- The panel does not have the token yet (a wrong one is refused). Instead, a
connection must say hello within 10 s, 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). What is left: while no
panel is connected (Premiere closed, the broker just started), any local program —
another Windows user's too — can register as the panel and read the requests sent
to it (never reaching Premiere); and the panel trusts whatever answers on
localhost:8017: while no broker runs, a program listening there receives the
real panel and can send it whitelisted commands. The panel itself refuses network
(UNC) and device paths in every param it hands Premiere (the spec's localPath),
so such a program cannot make Premiere offer your credentials to another host. The
broker does not start beside such a program: 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)"). On a machine you share, start the bridge before other
users' sessions.
- 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.
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.
Changelog
- 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 (, off by default).
New panel commands (phase 2): / (by guid, or by name
with ); and , a restore point before a risky
edit (renamed " [restore YYYY-MM-DD HH:MM]", tagged, not made active;
deletes only the oldest untouched restore points of that sequence);
(checks the file's time stamp); (finds the transition in an OTIO
export, names its owner, verifies nothing else went), (Motion, Opacity,
Time Remapping and the audio volume refused) and (a range or all,
), so extend_cut's KEYFRAMED refusal now hands over the exact
remove_keyframes command for each held key; (snapped to the item's frames,
read back) and (refused unless ffprobe finds the new file matches the old
media; lists every use in every sequence); (Premiere's and the user's
.epr presets, format decoded) and (renders through Premiere and checks
size, frame rate and length with ffprobe); and (by
the localised display name, e.g. "降噪"); 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 guard key the panel checks before any write
(; the same hash as get_sequence's,
pure-JS SHA-256); a 1000-action cap per undo step; checks room per track
in the panel too (ping ), 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: 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
(); 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:
Roadmap
- v0.7 — MCP: a stdio MCP server on the same
ppro_bridge library, with a
generic operation layer (apply_ops / query) next to the named commands.
- Hardening still to do: the panel and the broker proving the pairing token to
each other (today a program that answers on
localhost:8017 while no broker
runs gets the panel); 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.