premiere-pro-mcp 1.18.4 → 1.18.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +10 -10
- package/after-effects-cep-plugin/CSXS/manifest.xml +2 -2
- package/artifacts/MCPBridgeCEP.zxp +0 -0
- package/cep-plugin/CSXS/manifest.xml +3 -3
- package/cep-plugin/index.html +1 -1
- package/cep-plugin/updater.cjs +1 -1
- package/dist/bridge/script-builder.js +64 -0
- package/dist/tools/advanced.js +81 -12
- package/dist/tools/media.js +27 -2
- package/dist/tools/playback.js +12 -1
- package/dist/tools/project.d.ts +19 -2
- package/dist/tools/project.js +59 -6
- package/dist/tools/sequence.js +181 -24
- package/dist/tools/timeline.js +90 -15
- package/dist/tools/utility.d.ts +50 -4
- package/dist/tools/utility.js +182 -44
- package/dist/tools/uxp-advanced-workflows.js +2 -2
- package/dist/tools/uxp.js +1 -1
- package/docs/supported-actions.md +11 -11
- package/package.json +1 -1
- package/public-product-manifest.json +1 -1
- package/uxp-plugin/advanced-workflows.cjs +90 -14
- package/uxp-plugin/commands.cjs +67 -2
- package/uxp-plugin/index.cjs +2 -30
- package/uxp-plugin/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.18.5] - 2026-09-28
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `unnest_sequence` refuses before any change when the nest is trimmed, speed-changed or reversed, a target track is locked, or the target range already holds a clip. It now overwrites each nested clip's exact source range in place instead of inserting it, which pushed later clips down the track and ignored each clip's in and out. It reads every clip back afterwards and reports "The timeline changed … Use Undo" on any mismatch. Effects, keyframes, and transitions inside the nest are not carried over; the description says so. (#642)
|
|
14
|
+
- `move_clip_to_track` refuses before calling QE when either track is locked or the target range is occupied, finds the moved clip even when its node ID changes, and reports a copy left on the original track or a changed start, length, or in/out. It is now marked EXPERIMENTAL (QE) and rejects non-integer track numbers. (#642)
|
|
15
|
+
- `replace_clip` overwrites exactly the old clip's start and end instead of inserting the new item, which pushed later clips down the track and used the new item's full length. It verifies the span, that the old clip is gone, and that track clip counts are unchanged, and refuses on a locked track. The old clip's linked partner is left in place; the description says so. (#642)
|
|
16
|
+
- `export_frame_uxp` checks that the PNG exists after Premiere's exporter returns. It fails when no file was written, reports the real path when the host names the file differently (with or without the `.png` extension), and reports `committed_unverified` when UXP storage cannot check or the file already existed before the export. The panel's direct frame-export path now uses the same handler. (#642)
|
|
17
|
+
- `encode_media_uxp` results for jobs sent to Adobe Media Encoder carry `ameQueueStarted: "unknown"` and a note. Premiere 26.5.1 accepted UXP encodes into the Media Encoder queue without starting it. The tool does not start the queue through CEP; the note points to Media Encoder or `start_batch_encode`. (#642)
|
|
18
|
+
- `organize_project_items_uxp` remove verifies absence by walking the project tree instead of checking the Project panel selection first, which could still hold the removed item and turned a successful removal into `committed_unverified`. Remove now requires `project_item_id` and never falls back to the selection. (#642)
|
|
19
|
+
- `refresh_media` reads the interpreted frame rate before and after the refresh. When the refresh leaves an implausible rate (reported on stills as 29.97 becoming 2.75e-8), it restores the previous rate, reads it back, and reports `repaired: true`; if the restore does not stick, it fails with the manual fix. A real rate change from the source file is kept. (#642)
|
|
20
|
+
- `edit_timeline_uxp` MOGRT inserts (`insert_mogrt_path`, `insert_mogrt_library`) read the placement back instead of reporting the SequenceEditor return value. The result is `verified` when a returned item starts at the requested time, within one frame, on the requested video track. It fails with `UXP_VERIFICATION_FAILED` when nothing is there, and stays `committed_unverified` when the track cannot be read. Results include `sequenceId`, `requested`, and up to 16 `placements`. (#642)
|
|
21
|
+
- `set_sequence_pixel_aspect_ratio` compares the read-back ratio as a number, so a host that formats it as `1`, `1:1`, or `1.42222` no longer fails a correct update. A real mismatch reports the value Premiere read back. (#642)
|
|
22
|
+
- `play_source_monitor` fails with "No clip is loaded in the Source Monitor" instead of reporting a playback request that does nothing, names the loaded clip, and rejects a `speed` that is zero, not finite, or outside -64 to 64 before building a script. (#642)
|
|
23
|
+
- `create_bars_and_tone` takes an optional `bin_id`. The bin is resolved before anything is created, the new item is moved there, and its location is read back; the result is `committed_unverified` when the move cannot be confirmed. (#642)
|
|
24
|
+
- `get_unused_media` and `get_duplicate_media` page their output with `offset`, `limit` (1-500, default 100), and a case-insensitive `contains` name filter, and return `total`, `returned`, `truncated`, and `nextOffset`. Large projects no longer return unbounded lists. (#642)
|
|
25
|
+
- `get_duplicate_media` no longer groups different After Effects compositions imported from the same `.aep` or `.aepx` as duplicates. The same composition imported twice is still reported. (#642)
|
|
26
|
+
- `get_project_panel_metadata` caps the returned XML at `max_chars` (256-200000, default 20000) and reports `truncated` and `totalChars`. Truncated XML is marked as unsafe to pass back to `set_project_panel_metadata`. (#642)
|
|
27
|
+
|
|
28
|
+
### Verification scope
|
|
29
|
+
|
|
30
|
+
- Automated checks validate package behavior. These changes follow live reports from Premiere 26.5.1 but have not been re-verified on a licensed Premiere host. The `unnest_sequence` and `replace_clip` overwrite path (`Track.overwriteClip` with source In/Out marks) has only been exercised against test fakes; its readback reports any host difference as a failure.
|
|
31
|
+
|
|
9
32
|
## [1.18.4] - 2026-09-28
|
|
10
33
|
|
|
11
34
|
### Fixed
|
package/README.md
CHANGED
|
@@ -110,9 +110,9 @@ The current source exposes 384 core tools for supported workflow steps spanning
|
|
|
110
110
|
|
|
111
111
|
<a id="latest-release"></a>
|
|
112
112
|
|
|
113
|
-
### Latest release: 1.18.
|
|
113
|
+
### Latest release: 1.18.4
|
|
114
114
|
|
|
115
|
-
The published v1.18.
|
|
115
|
+
The published v1.18.4 npm artifact contains **384 core tools**, 382 in its default profile,
|
|
116
116
|
and 478 with a compatible UXP connection. The development catalog above can include
|
|
117
117
|
unreleased work. See the [versioned facts and package provenance](https://premiere-pro-mcp.com/facts/).
|
|
118
118
|
|
|
@@ -162,7 +162,7 @@ if the connection is unavailable.
|
|
|
162
162
|
local Premiere processes. See the generated [supported action catalog](docs/supported-actions.md)
|
|
163
163
|
for individual capability and verification contracts.
|
|
164
164
|
|
|
165
|
-
See the [v1.18.
|
|
165
|
+
See the [v1.18.4 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.18.4)
|
|
166
166
|
for complete details. Live installation in Premiere Pro still requires host verification.
|
|
167
167
|
|
|
168
168
|
### Current MCP protocol support
|
|
@@ -210,7 +210,7 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
210
210
|
> ### Install the published package (verify the name)
|
|
211
211
|
>
|
|
212
212
|
> ```bash
|
|
213
|
-
> npm i -g premiere-pro-mcp@1.18.
|
|
213
|
+
> npm i -g premiere-pro-mcp@1.18.4
|
|
214
214
|
> ```
|
|
215
215
|
>
|
|
216
216
|
> This repository publishes only **`premiere-pro-mcp`**. A differently named package (`adobe-premiere-pro-mcp`) may also declare a `premiere-pro-mcp` executable. Before configuring a client, confirm:
|
|
@@ -218,7 +218,7 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
218
218
|
> | Check | Expected |
|
|
219
219
|
> | --- | --- |
|
|
220
220
|
> | Package name | `premiere-pro-mcp` (not `adobe-premiere-pro-mcp`) |
|
|
221
|
-
> | Version | `1.18.
|
|
221
|
+
> | Version | `1.18.4` |
|
|
222
222
|
> | Homepage / repo | https://premiere-pro-mcp.com/ · https://github.com/leancoderkavy/premiere-pro-mcp |
|
|
223
223
|
>
|
|
224
224
|
> ```bash
|
|
@@ -231,9 +231,9 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
231
231
|
|
|
232
232
|
### Easiest supported path: Claude Desktop
|
|
233
233
|
|
|
234
|
-
1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.18.
|
|
234
|
+
1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.18.4/premiere-pro-mcp-1.18.4.mcpb).
|
|
235
235
|
2. In Claude Desktop, open **Settings > Extensions > Advanced settings > Install Extension**, select the downloaded bundle, and restart Claude Desktop.
|
|
236
|
-
3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.18.
|
|
236
|
+
3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.18.4/MCPBridgeCEP.zxp). Open it with your trusted ZXP installer. If your computer has no ZXP installer, use the npm connector installer in **Advanced setup** below.
|
|
237
237
|
4. Restart Premiere, open a project, then open **Window > Extensions > MCP for Adobe Premiere Pro**.
|
|
238
238
|
5. In Claude, enter: `Safely check my Premiere connection with verify_premiere_connection. Make no changes.`
|
|
239
239
|
|
|
@@ -533,11 +533,11 @@ From a clone of this repository:
|
|
|
533
533
|
```bash
|
|
534
534
|
codex plugin marketplace add .
|
|
535
535
|
codex plugin add premiere-pro@premiere-pro-mcp
|
|
536
|
-
npx -y premiere-pro-mcp@1.18.
|
|
536
|
+
npx -y premiere-pro-mcp@1.18.4 --install-cep
|
|
537
537
|
```
|
|
538
538
|
|
|
539
539
|
Restart Premiere Pro and start a new Codex session after installation. The plugin
|
|
540
|
-
launches `premiere-pro-mcp@1.18.
|
|
540
|
+
launches `premiere-pro-mcp@1.18.4` through `npx`; the separate CEP installation is
|
|
541
541
|
required because the MCP server communicates with the running Premiere host through
|
|
542
542
|
the local bridge.
|
|
543
543
|
|
|
@@ -575,7 +575,7 @@ For Claude Code, add this repository as a marketplace and install the plugin:
|
|
|
575
575
|
Then install the Premiere bridge and start a new Claude Code session:
|
|
576
576
|
|
|
577
577
|
```bash
|
|
578
|
-
npx -y premiere-pro-mcp@1.18.
|
|
578
|
+
npx -y premiere-pro-mcp@1.18.4 --install-cep
|
|
579
579
|
```
|
|
580
580
|
|
|
581
581
|
The Claude Code package lives in
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.18.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.18.5" ExtensionBundleName="MCP for Adobe After Effects">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.18.
|
|
4
|
+
<Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.18.5"/>
|
|
5
5
|
</ExtensionList>
|
|
6
6
|
<ExecutionEnvironment>
|
|
7
7
|
<HostList>
|
|
Binary file
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.18.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.18.5" ExtensionBundleName="MCP for Adobe Premiere Pro">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.18.
|
|
5
|
-
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.18.
|
|
4
|
+
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.18.5"/>
|
|
5
|
+
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.18.5"/>
|
|
6
6
|
</ExtensionList>
|
|
7
7
|
<ExecutionEnvironment>
|
|
8
8
|
<HostList>
|
package/cep-plugin/index.html
CHANGED
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
<section class="update-section" aria-live="polite">
|
|
82
82
|
<div class="update-copy">
|
|
83
83
|
<span class="section-label">MCP updates</span>
|
|
84
|
-
<strong id="updateTitle">Version 1.18.
|
|
84
|
+
<strong id="updateTitle">Version 1.18.5</strong>
|
|
85
85
|
<span id="updateDetail">Checking the global MCP server and connector release…</span>
|
|
86
86
|
</div>
|
|
87
87
|
<button id="btnUpdate" class="button button-update" onclick="handleUpdateClick()" type="button" aria-describedby="updateDetail" disabled>
|
package/cep-plugin/updater.cjs
CHANGED
|
@@ -72,6 +72,70 @@ function __writeClipSpan(item, startTicks, endTicks) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// Tick comparisons for values that round-trip through seconds (ProjectItem
|
|
76
|
+
// marks take seconds). Far below one frame at any frame rate.
|
|
77
|
+
var __TICK_MATCH_TOL = 16;
|
|
78
|
+
|
|
79
|
+
function __isTrackLocked(track) {
|
|
80
|
+
try {
|
|
81
|
+
if (track && typeof track.isLocked === "function") return !!track.isLocked();
|
|
82
|
+
} catch (eLocked) {}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Place exactly [inTicks, outTicks) of a project item on ONE track at
|
|
87
|
+
// startTicks without rippling. Track.overwriteClip places the item's In/Out
|
|
88
|
+
// range, so set those marks first and restore the item's own marks afterwards.
|
|
89
|
+
// mediaType: 1 = video, 2 = audio. Callers must prove the destination range is
|
|
90
|
+
// empty first; overwrite never shifts neighbours but would replace them.
|
|
91
|
+
// Returns { ok, attempted, marksRestored, error }. attempted=true means the
|
|
92
|
+
// overwrite call ran (or threw), so the timeline may have changed.
|
|
93
|
+
function __overwriteRangeOnTrack(track, item, startTicks, inTicks, outTicks, mediaType) {
|
|
94
|
+
if (!track || typeof track.overwriteClip !== "function") {
|
|
95
|
+
return { ok: false, attempted: false, marksRestored: true, error: "Track.overwriteClip is unavailable on this Premiere build" };
|
|
96
|
+
}
|
|
97
|
+
if (!item || typeof item.getInPoint !== "function" || typeof item.getOutPoint !== "function" ||
|
|
98
|
+
typeof item.setInPoint !== "function" || typeof item.setOutPoint !== "function") {
|
|
99
|
+
return { ok: false, attempted: false, marksRestored: true, error: "Project item In/Out marks cannot be set on this Premiere build" };
|
|
100
|
+
}
|
|
101
|
+
var originalIn = null;
|
|
102
|
+
var originalOut = null;
|
|
103
|
+
try {
|
|
104
|
+
originalIn = String(item.getInPoint(mediaType).ticks);
|
|
105
|
+
originalOut = String(item.getOutPoint(mediaType).ticks);
|
|
106
|
+
} catch (eMarks) {
|
|
107
|
+
return { ok: false, attempted: false, marksRestored: true, error: "Could not read the project item's In/Out marks" };
|
|
108
|
+
}
|
|
109
|
+
function setMarks(wantIn, wantOut) {
|
|
110
|
+
var currentOut = parseFloat(item.getOutPoint(mediaType).ticks);
|
|
111
|
+
if (parseFloat(wantIn) >= currentOut) {
|
|
112
|
+
item.setOutPoint(__ticksToSeconds(wantOut), mediaType);
|
|
113
|
+
item.setInPoint(__ticksToSeconds(wantIn), mediaType);
|
|
114
|
+
} else {
|
|
115
|
+
item.setInPoint(__ticksToSeconds(wantIn), mediaType);
|
|
116
|
+
item.setOutPoint(__ticksToSeconds(wantOut), mediaType);
|
|
117
|
+
}
|
|
118
|
+
return Math.abs(parseFloat(item.getInPoint(mediaType).ticks) - parseFloat(wantIn)) <= __TICK_MATCH_TOL &&
|
|
119
|
+
Math.abs(parseFloat(item.getOutPoint(mediaType).ticks) - parseFloat(wantOut)) <= __TICK_MATCH_TOL;
|
|
120
|
+
}
|
|
121
|
+
function restore() {
|
|
122
|
+
try { return setMarks(originalIn, originalOut); } catch (eRestore) { return false; }
|
|
123
|
+
}
|
|
124
|
+
var applied = false;
|
|
125
|
+
try { applied = setMarks(inTicks, outTicks); } catch (eSet) { applied = false; }
|
|
126
|
+
if (!applied) {
|
|
127
|
+
return { ok: false, attempted: false, marksRestored: restore(), error: "Premiere did not apply the source In/Out range to project item " + item.name };
|
|
128
|
+
}
|
|
129
|
+
var at = new Time();
|
|
130
|
+
at.ticks = String(startTicks);
|
|
131
|
+
try {
|
|
132
|
+
track.overwriteClip(item, at);
|
|
133
|
+
} catch (eOverwrite) {
|
|
134
|
+
return { ok: false, attempted: true, marksRestored: restore(), error: "Track.overwriteClip rejected project item " + item.name + ": " + eOverwrite.toString() };
|
|
135
|
+
}
|
|
136
|
+
return { ok: true, attempted: true, marksRestored: restore(), error: "" };
|
|
137
|
+
}
|
|
138
|
+
|
|
75
139
|
function __ticksToTimecode(ticks, fps) {
|
|
76
140
|
var totalSeconds = __ticksToSeconds(ticks);
|
|
77
141
|
var hours = Math.floor(totalSeconds / 3600);
|
package/dist/tools/advanced.js
CHANGED
|
@@ -515,7 +515,9 @@ export function getAdvancedTools(bridgeOptions) {
|
|
|
515
515
|
},
|
|
516
516
|
},
|
|
517
517
|
move_clip_to_track: {
|
|
518
|
-
description: "Move a clip to a different track.
|
|
518
|
+
description: "Move a clip to a different track of the same type, keeping its start, duration, and source in/out. EXPERIMENTAL: uses the undocumented QE DOM moveToTrack. " +
|
|
519
|
+
"Refuses without changing anything when the origin or destination track is locked or the destination range is occupied. " +
|
|
520
|
+
"Reads the timeline back: verified only when the clip is on the destination track with the same span and source range and is gone from the origin track; committed_unverified when the source range cannot be read; otherwise failure with Undo guidance.",
|
|
519
521
|
parameters: {
|
|
520
522
|
type: "object",
|
|
521
523
|
properties: {
|
|
@@ -531,6 +533,9 @@ export function getAdvancedTools(bridgeOptions) {
|
|
|
531
533
|
required: ["node_id", "target_track_index"],
|
|
532
534
|
},
|
|
533
535
|
handler: async (args) => {
|
|
536
|
+
if (!Number.isSafeInteger(args.target_track_index) || args.target_track_index < 0) {
|
|
537
|
+
return { success: false, error: "target_track_index must be a non-negative integer" };
|
|
538
|
+
}
|
|
534
539
|
const nodeId = escapeForExtendScript(args.node_id);
|
|
535
540
|
const script = buildToolScript(`
|
|
536
541
|
app.enableQE();
|
|
@@ -549,6 +554,47 @@ export function getAdvancedTools(bridgeOptions) {
|
|
|
549
554
|
return __result({ moved: false, verified: true, alreadyOnTrack: true, clipName: result.clip.name, trackIndex: result.trackIndex });
|
|
550
555
|
}
|
|
551
556
|
|
|
557
|
+
// Preflight: refuse before any QE call when either track is locked or
|
|
558
|
+
// the destination range holds another clip (QE would overwrite it).
|
|
559
|
+
var originTrack = targetTracks[result.trackIndex];
|
|
560
|
+
var destTrack = targetTracks[${args.target_track_index}];
|
|
561
|
+
if (__isTrackLocked(originTrack)) {
|
|
562
|
+
return __error("Move refused; nothing was changed. Origin " + result.trackType + " track " + result.trackIndex + " is locked.");
|
|
563
|
+
}
|
|
564
|
+
if (__isTrackLocked(destTrack)) {
|
|
565
|
+
return __error("Move refused; nothing was changed. Destination " + result.trackType + " track ${args.target_track_index} is locked.");
|
|
566
|
+
}
|
|
567
|
+
var moveStart = parseFloat(result.clip.start.ticks);
|
|
568
|
+
var moveEnd = parseFloat(result.clip.end.ticks);
|
|
569
|
+
for (var oi = 0; oi < destTrack.clips.numItems; oi++) {
|
|
570
|
+
var occupant = destTrack.clips[oi];
|
|
571
|
+
var occupantStart = parseFloat(occupant.start.ticks);
|
|
572
|
+
var occupantEnd = parseFloat(occupant.end.ticks);
|
|
573
|
+
if (occupantStart < moveEnd - __TICK_MATCH_TOL && occupantEnd > moveStart + __TICK_MATCH_TOL) {
|
|
574
|
+
return __error("Move refused; nothing was changed. The destination range " + __ticksToSeconds(moveStart) + "s-" + __ticksToSeconds(moveEnd) + "s on " + result.trackType + " track ${args.target_track_index} is already occupied by " + occupant.name + ", and moving there would overwrite it.");
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
var movedName = result.clip.name;
|
|
578
|
+
var movedSourceId = "";
|
|
579
|
+
try { movedSourceId = result.clip.projectItem ? String(result.clip.projectItem.nodeId) : ""; } catch (eMovedSource) {}
|
|
580
|
+
|
|
581
|
+
// Premiere can mint a new node ID when a clip changes track, so fall
|
|
582
|
+
// back to matching the same source item, name, and start on a track.
|
|
583
|
+
function findMovedOn(trackIndex) {
|
|
584
|
+
var track = targetTracks[trackIndex];
|
|
585
|
+
for (var fi = 0; fi < track.clips.numItems; fi++) {
|
|
586
|
+
var cand = track.clips[fi];
|
|
587
|
+
if (String(cand.nodeId) === "${nodeId}") return { clip: cand, trackIndex: trackIndex, trackType: result.trackType };
|
|
588
|
+
var candSource = "";
|
|
589
|
+
try { candSource = cand.projectItem ? String(cand.projectItem.nodeId) : ""; } catch (eCandSource) {}
|
|
590
|
+
if (movedSourceId && candSource === movedSourceId && cand.name === movedName &&
|
|
591
|
+
Math.abs(parseFloat(cand.start.ticks) - moveStart) <= __TICK_MATCH_TOL) {
|
|
592
|
+
return { clip: cand, trackIndex: trackIndex, trackType: result.trackType };
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
|
|
552
598
|
var qeTrack = result.trackType === "video"
|
|
553
599
|
? qeSeq.getVideoTrackAt(result.trackIndex)
|
|
554
600
|
: qeSeq.getAudioTrackAt(result.trackIndex);
|
|
@@ -586,41 +632,64 @@ export function getAdvancedTools(bridgeOptions) {
|
|
|
586
632
|
|
|
587
633
|
// QE reports nothing useful on success, so confirm against the DOM.
|
|
588
634
|
var after = __findClip("${nodeId}");
|
|
589
|
-
if (!after)
|
|
635
|
+
if (!after) after = findMovedOn(${args.target_track_index});
|
|
636
|
+
if (!after) return __error("The timeline changed: clip " + movedName + " could not be found on its original or destination track after the track move. Use Undo and check the timeline.");
|
|
590
637
|
if (after.trackIndex !== ${args.target_track_index}) {
|
|
638
|
+
if (findMovedOn(${args.target_track_index})) {
|
|
639
|
+
return __error("The timeline changed: Premiere placed a copy of " + movedName + " on track ${args.target_track_index} but the original is still on track " + after.trackIndex + ". Use Undo and move the clip in the Premiere UI.");
|
|
640
|
+
}
|
|
591
641
|
return __error("Premiere accepted the moveToTrack call but the clip is still on track " + after.trackIndex + " rather than ${args.target_track_index}. Structural QE edits are known to no-op on some Premiere Pro 26.x installations (confirmed on 26.2.2).");
|
|
592
642
|
}
|
|
643
|
+
if (findMovedOn(result.trackIndex)) {
|
|
644
|
+
return __error("The timeline changed: " + movedName + " now exists on both track " + result.trackIndex + " and track ${args.target_track_index}. Use Undo and move the clip in the Premiere UI.");
|
|
645
|
+
}
|
|
593
646
|
// moveToTrack can rewrite end independently of start (#550). Re-assert
|
|
594
647
|
// the original span before verifying, then fail closed if it did not hold.
|
|
595
648
|
if (String(after.clip.start.ticks) !== beforeMoveStartTicks || String(after.clip.end.ticks) !== beforeMoveEndTicks) {
|
|
596
649
|
try {
|
|
597
650
|
__writeClipSpan(after.clip, beforeMoveStartTicks, beforeMoveEndTicks);
|
|
598
651
|
} catch (spanErr) {
|
|
599
|
-
return __error("Premiere changed the clip's timeline range during the track move and it could not be restored (" + spanErr.toString() + "). Use Undo and retry in the Premiere UI.");
|
|
652
|
+
return __error("The timeline changed: Premiere changed the clip's timeline range during the track move and it could not be restored (" + spanErr.toString() + "). Use Undo and retry in the Premiere UI.");
|
|
600
653
|
}
|
|
601
|
-
|
|
602
|
-
|
|
654
|
+
var restored = __findClip("${nodeId}");
|
|
655
|
+
after = restored || findMovedOn(${args.target_track_index});
|
|
656
|
+
if (!after) return __error("The timeline changed: clip " + movedName + " could not be found after restoring its timeline range. Use Undo and check the timeline.");
|
|
603
657
|
}
|
|
604
658
|
var afterMoveStartTicks = String(after.clip.start.ticks);
|
|
605
659
|
var afterMoveEndTicks = String(after.clip.end.ticks);
|
|
606
660
|
if (parseFloat(afterMoveStartTicks) >= parseFloat(afterMoveEndTicks)) {
|
|
607
|
-
return __error("Premiere left clip
|
|
661
|
+
return __error("The timeline changed: Premiere left clip " + movedName + " with an inverted or empty timeline range after the track move. Use Undo and retry in the Premiere UI.");
|
|
662
|
+
}
|
|
663
|
+
if (Math.abs(parseFloat(afterMoveStartTicks) - parseFloat(beforeMoveStartTicks)) > 1) {
|
|
664
|
+
return __error("The timeline changed: Premiere moved the clip to " + __ticksToSeconds(afterMoveStartTicks) + "s instead of keeping its start at " + __ticksToSeconds(beforeMoveStartTicks) + "s. Use Undo and retry in the Premiere UI.");
|
|
608
665
|
}
|
|
609
666
|
if (Math.abs((parseFloat(afterMoveEndTicks) - parseFloat(afterMoveStartTicks)) - spanTicks) > 1) {
|
|
610
|
-
return __error("Premiere changed the clip duration during the track move. Use Undo and retry in the Premiere UI.");
|
|
667
|
+
return __error("The timeline changed: Premiere changed the clip duration during the track move. Use Undo and retry in the Premiere UI.");
|
|
611
668
|
}
|
|
612
|
-
|
|
613
|
-
|
|
669
|
+
var sourceReadable = true;
|
|
670
|
+
var afterInTicks = "";
|
|
671
|
+
var afterOutTicks = "";
|
|
672
|
+
try {
|
|
673
|
+
afterInTicks = String(after.clip.inPoint.ticks);
|
|
674
|
+
afterOutTicks = String(after.clip.outPoint.ticks);
|
|
675
|
+
} catch (eSourceRead) {
|
|
676
|
+
sourceReadable = false;
|
|
677
|
+
}
|
|
678
|
+
if (sourceReadable && (afterInTicks !== beforeMoveInTicks || afterOutTicks !== beforeMoveOutTicks)) {
|
|
679
|
+
return __error("The timeline changed: Premiere changed the clip's source in/out points during the track move. Use Undo and retry in the Premiere UI.");
|
|
614
680
|
}
|
|
615
681
|
|
|
616
|
-
|
|
682
|
+
var moveResult = {
|
|
617
683
|
moved: true,
|
|
618
|
-
verified:
|
|
684
|
+
verified: sourceReadable,
|
|
685
|
+
outcome: sourceReadable ? "verified" : "committed_unverified",
|
|
619
686
|
clipName: after.clip.name,
|
|
620
687
|
newTrackIndex: after.trackIndex,
|
|
621
688
|
startSeconds: __ticksToSeconds(afterMoveStartTicks),
|
|
622
689
|
endSeconds: __ticksToSeconds(afterMoveEndTicks)
|
|
623
|
-
}
|
|
690
|
+
};
|
|
691
|
+
if (!sourceReadable) moveResult.warning = "The clip is on the destination track with the same start and duration, but its source in/out points could not be read back.";
|
|
692
|
+
return __result(moveResult);
|
|
624
693
|
`);
|
|
625
694
|
return sendCommand(script, bridgeOptions);
|
|
626
695
|
},
|
package/dist/tools/media.js
CHANGED
|
@@ -262,7 +262,7 @@ export function getMediaTools(bridgeOptions) {
|
|
|
262
262
|
},
|
|
263
263
|
},
|
|
264
264
|
refresh_media: {
|
|
265
|
-
description: "Refresh a project item to pick up changes to the source file",
|
|
265
|
+
description: "Refresh a project item to pick up changes to the source file. The interpreted frame rate is read before and after; if the refresh leaves an implausible rate (seen on stills, e.g. 29.97 becoming 2.75e-8), the previous rate is restored and read back. Reports frameRateBefore, frameRateAfter and repaired.",
|
|
266
266
|
parameters: {
|
|
267
267
|
type: "object",
|
|
268
268
|
properties: {
|
|
@@ -277,8 +277,33 @@ export function getMediaTools(bridgeOptions) {
|
|
|
277
277
|
const script = buildToolScript(`
|
|
278
278
|
var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
|
|
279
279
|
if (!item) return __error("Item not found: ${escapeForExtendScript(args.item_id)}");
|
|
280
|
+
// Some hosts corrupt the interpreted frame rate of stills on refresh
|
|
281
|
+
// (#642). Snapshot it, and restore it only when the new value is not a
|
|
282
|
+
// usable rate, so a real change in the source file is kept.
|
|
283
|
+
var readRate = function () {
|
|
284
|
+
try {
|
|
285
|
+
var interp = item.getFootageInterpretation();
|
|
286
|
+
var rate = interp ? Number(interp.frameRate) : NaN;
|
|
287
|
+
return isFinite(rate) ? rate : null;
|
|
288
|
+
} catch (eRate) { return null; }
|
|
289
|
+
};
|
|
290
|
+
var usableRate = function (rate) { return rate !== null && rate >= 1 && rate <= 1000; };
|
|
291
|
+
var before = readRate();
|
|
280
292
|
item.refreshMedia();
|
|
281
|
-
|
|
293
|
+
var after = readRate();
|
|
294
|
+
if (!usableRate(before) || usableRate(after)) {
|
|
295
|
+
return __result({ refreshed: true, item: item.name, frameRateBefore: before, frameRateAfter: after, repaired: false });
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
var restore = item.getFootageInterpretation();
|
|
299
|
+
restore.frameRate = before;
|
|
300
|
+
item.setFootageInterpretation(restore);
|
|
301
|
+
} catch (eRestore) {}
|
|
302
|
+
var restored = readRate();
|
|
303
|
+
if (restored === null || Math.abs(restored - before) > 0.001) {
|
|
304
|
+
return __error("refresh_media refreshed " + item.name + ", but its frame rate changed from " + before + " to " + after + " and could not be restored (read back " + restored + "). Set it with set_footage_interpretation frame_rate " + before + ".");
|
|
305
|
+
}
|
|
306
|
+
return __result({ refreshed: true, item: item.name, frameRateBefore: before, frameRateAfter: restored, frameRateAfterRefresh: after, repaired: true });
|
|
282
307
|
`);
|
|
283
308
|
return sendCommand(script, bridgeOptions);
|
|
284
309
|
},
|
package/dist/tools/playback.js
CHANGED
|
@@ -35,7 +35,7 @@ export function getPlaybackTools(bridgeOptions) {
|
|
|
35
35
|
},
|
|
36
36
|
},
|
|
37
37
|
play_source_monitor: {
|
|
38
|
-
description: "Request playback of the clip in the Source Monitor. The legacy API does not provide a same-call position readback, so movement is not reported as verified.",
|
|
38
|
+
description: "Request playback of the clip in the Source Monitor. Fails when no clip is loaded. The legacy API does not provide a same-call position readback, so movement is not reported as verified.",
|
|
39
39
|
parameters: {
|
|
40
40
|
type: "object",
|
|
41
41
|
properties: {
|
|
@@ -47,12 +47,23 @@ export function getPlaybackTools(bridgeOptions) {
|
|
|
47
47
|
},
|
|
48
48
|
handler: async (args) => {
|
|
49
49
|
const speed = args.speed ?? 1.0;
|
|
50
|
+
if (typeof speed !== "number" || !Number.isFinite(speed) || speed === 0 || Math.abs(speed) > 64) {
|
|
51
|
+
return { success: false, error: "speed must be a non-zero number from -64 to 64." };
|
|
52
|
+
}
|
|
50
53
|
const script = buildToolScript(`
|
|
54
|
+
// Premiere accepts play() with nothing loaded and does nothing (#642).
|
|
55
|
+
var loaded = null;
|
|
56
|
+
try {
|
|
57
|
+
if (typeof app.sourceMonitor.getProjectItem === "function") loaded = app.sourceMonitor.getProjectItem();
|
|
58
|
+
else loaded = undefined;
|
|
59
|
+
} catch (eLoaded) { loaded = undefined; }
|
|
60
|
+
if (loaded === null) return __error("No clip is loaded in the Source Monitor. Open one with open_in_source first.");
|
|
51
61
|
app.sourceMonitor.play(${speed});
|
|
52
62
|
return __result({
|
|
53
63
|
playbackRequested: true,
|
|
54
64
|
playbackVerified: false,
|
|
55
65
|
speed: ${speed},
|
|
66
|
+
clip: loaded ? loaded.name : null,
|
|
56
67
|
verificationScope: "Premiere accepted the source-monitor playback request only; poll get_source_monitor_position before treating movement as confirmed."
|
|
57
68
|
});
|
|
58
69
|
`);
|
package/dist/tools/project.d.ts
CHANGED
|
@@ -298,9 +298,14 @@ export declare function getProjectTools(bridgeOptions: BridgeOptions): {
|
|
|
298
298
|
type: string;
|
|
299
299
|
description: string;
|
|
300
300
|
};
|
|
301
|
+
bin_id: {
|
|
302
|
+
type: string;
|
|
303
|
+
description: string;
|
|
304
|
+
};
|
|
301
305
|
};
|
|
302
306
|
};
|
|
303
307
|
handler: (args: {
|
|
308
|
+
bin_id?: string;
|
|
304
309
|
width?: number;
|
|
305
310
|
height?: number;
|
|
306
311
|
timebase?: string;
|
|
@@ -354,8 +359,20 @@ export declare function getProjectTools(bridgeOptions: BridgeOptions): {
|
|
|
354
359
|
};
|
|
355
360
|
get_project_panel_metadata: {
|
|
356
361
|
description: string;
|
|
357
|
-
parameters: {
|
|
358
|
-
|
|
362
|
+
parameters: {
|
|
363
|
+
type: "object";
|
|
364
|
+
properties: {
|
|
365
|
+
max_chars: {
|
|
366
|
+
type: string;
|
|
367
|
+
minimum: number;
|
|
368
|
+
maximum: number;
|
|
369
|
+
description: string;
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
handler: (args?: {
|
|
374
|
+
max_chars?: number;
|
|
375
|
+
}) => Promise<import("../bridge/file-bridge.js").CommandResult>;
|
|
359
376
|
};
|
|
360
377
|
set_project_panel_metadata: {
|
|
361
378
|
description: string;
|
package/dist/tools/project.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { buildToolScript, escapeForExtendScript, } from "../bridge/script-builder.js";
|
|
2
2
|
import { sendCommand } from "../bridge/file-bridge.js";
|
|
3
|
+
const PROJECT_PANEL_METADATA_MIN_CHARS = 256;
|
|
4
|
+
const PROJECT_PANEL_METADATA_MAX_CHARS = 200000;
|
|
5
|
+
const PROJECT_PANEL_METADATA_DEFAULT_CHARS = 20000;
|
|
3
6
|
export function getProjectTools(bridgeOptions) {
|
|
4
7
|
return {
|
|
5
8
|
save_project: {
|
|
@@ -596,6 +599,10 @@ export function getProjectTools(bridgeOptions) {
|
|
|
596
599
|
type: "string",
|
|
597
600
|
description: "Name for the bars and tone item (default: 'Bars and Tone')",
|
|
598
601
|
},
|
|
602
|
+
bin_id: {
|
|
603
|
+
type: "string",
|
|
604
|
+
description: "Optional destination bin: node ID, slash-separated bin path from the project root, or bin name. The bin is resolved before anything is created; the item is moved there and its location read back.",
|
|
605
|
+
},
|
|
599
606
|
},
|
|
600
607
|
},
|
|
601
608
|
handler: async (args) => {
|
|
@@ -615,6 +622,9 @@ export function getProjectTools(bridgeOptions) {
|
|
|
615
622
|
var timebase = parseFloat(${args.timebase ? `"${escapeForExtendScript(args.timebase)}"` : `seq ? seq.timebase : "254016000000"`});
|
|
616
623
|
if (!isFinite(timebase) || timebase <= 0) return __error("A positive sequence timebase is required to create bars and tone.");
|
|
617
624
|
var requestedName = "${escapeForExtendScript(name)}";
|
|
625
|
+
var targetBin = null;
|
|
626
|
+
${args.bin_id !== undefined ? `targetBin = __findBin("${escapeForExtendScript(String(args.bin_id))}");
|
|
627
|
+
if (!targetBin) return __error("Bin not found: ${escapeForExtendScript(String(args.bin_id))}. Nothing was created.");` : ""}
|
|
618
628
|
// newBarsAndTone does not reliably return a ProjectItem with name and
|
|
619
629
|
// nodeId (issue #588), so diff the project tree to find what it made.
|
|
620
630
|
var beforeIds = __collectNodeIds(app.project.rootItem, {});
|
|
@@ -638,12 +648,22 @@ export function getProjectTools(bridgeOptions) {
|
|
|
638
648
|
}
|
|
639
649
|
var itemName = requestedName;
|
|
640
650
|
try { itemName = item.name; } catch (eName) {}
|
|
651
|
+
var binPlacement = null;
|
|
652
|
+
if (targetBin) {
|
|
653
|
+
try { item.moveBin(targetBin); } catch (eMove) {}
|
|
654
|
+
var inBin = false;
|
|
655
|
+
try { inBin = String(item.treePath).indexOf(String(targetBin.treePath) + "\\\\") === 0; } catch (eInBin) {}
|
|
656
|
+
binPlacement = { bin: targetBin.name, moved: inBin };
|
|
657
|
+
}
|
|
641
658
|
var treePath = null;
|
|
642
659
|
try { treePath = item.treePath; } catch (eTreePath) {}
|
|
660
|
+
var placedOk = !binPlacement || binPlacement.moved;
|
|
643
661
|
return __result({
|
|
644
662
|
created: true,
|
|
645
|
-
verified:
|
|
646
|
-
outcome: "verified",
|
|
663
|
+
verified: placedOk,
|
|
664
|
+
outcome: placedOk ? "verified" : "committed_unverified",
|
|
665
|
+
bin: binPlacement,
|
|
666
|
+
note: placedOk ? null : "The item was created but could not be confirmed in the requested bin; move it with move_item_to_bin.",
|
|
647
667
|
name: itemName,
|
|
648
668
|
nodeId: __nodeIdOf(item),
|
|
649
669
|
treePath: treePath,
|
|
@@ -766,13 +786,46 @@ export function getProjectTools(bridgeOptions) {
|
|
|
766
786
|
},
|
|
767
787
|
},
|
|
768
788
|
get_project_panel_metadata: {
|
|
769
|
-
description: "Get the current Project-panel column layout as XML. This is schema/layout configuration, not per-item Scene/Shot/Take values; use inspect_project_panel_metadata_uxp item_columns or get_metadata for those.",
|
|
770
|
-
parameters: {
|
|
771
|
-
|
|
789
|
+
description: "Get the current Project-panel column layout as XML. This is schema/layout configuration, not per-item Scene/Shot/Take values; use inspect_project_panel_metadata_uxp item_columns or get_metadata for those. Output is capped by max_chars (default 20000); when truncated is true the XML is incomplete and must not be passed to set_project_panel_metadata.",
|
|
790
|
+
parameters: {
|
|
791
|
+
type: "object",
|
|
792
|
+
properties: {
|
|
793
|
+
max_chars: {
|
|
794
|
+
type: "integer",
|
|
795
|
+
minimum: PROJECT_PANEL_METADATA_MIN_CHARS,
|
|
796
|
+
maximum: PROJECT_PANEL_METADATA_MAX_CHARS,
|
|
797
|
+
description: `Maximum characters of metadata XML to return (${PROJECT_PANEL_METADATA_MIN_CHARS}-${PROJECT_PANEL_METADATA_MAX_CHARS}, default ${PROJECT_PANEL_METADATA_DEFAULT_CHARS}). Longer XML is cut at this length and reported with truncated: true and totalChars.`,
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
},
|
|
801
|
+
handler: async (args = {}) => {
|
|
802
|
+
const maxChars = args?.max_chars ?? PROJECT_PANEL_METADATA_DEFAULT_CHARS;
|
|
803
|
+
if (typeof maxChars !== "number" ||
|
|
804
|
+
!Number.isInteger(maxChars) ||
|
|
805
|
+
maxChars < PROJECT_PANEL_METADATA_MIN_CHARS ||
|
|
806
|
+
maxChars > PROJECT_PANEL_METADATA_MAX_CHARS) {
|
|
807
|
+
return {
|
|
808
|
+
success: false,
|
|
809
|
+
error: `max_chars must be an integer between ${PROJECT_PANEL_METADATA_MIN_CHARS} and ${PROJECT_PANEL_METADATA_MAX_CHARS}`,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
772
812
|
const script = buildToolScript(`
|
|
773
813
|
var meta = app.project.getProjectPanelMetadata();
|
|
774
814
|
if (!meta) return __error("Could not retrieve project panel metadata");
|
|
775
|
-
|
|
815
|
+
var text = String(meta);
|
|
816
|
+
var maxChars = ${maxChars};
|
|
817
|
+
var totalChars = text.length;
|
|
818
|
+
if (totalChars > maxChars) {
|
|
819
|
+
return __result({
|
|
820
|
+
metadata: text.substring(0, maxChars),
|
|
821
|
+
truncated: true,
|
|
822
|
+
totalChars: totalChars,
|
|
823
|
+
returnedChars: maxChars,
|
|
824
|
+
maxChars: maxChars,
|
|
825
|
+
note: "Metadata XML was truncated and is not well-formed. Raise max_chars (up to ${PROJECT_PANEL_METADATA_MAX_CHARS}) to read more; do not pass truncated XML to set_project_panel_metadata."
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
return __result({ metadata: text, truncated: false, totalChars: totalChars, returnedChars: totalChars, maxChars: maxChars });
|
|
776
829
|
`);
|
|
777
830
|
return sendCommand(script, bridgeOptions);
|
|
778
831
|
},
|