premiere-pro-mcp 1.23.0 → 1.24.0
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 +14 -0
- package/README.md +18 -12
- package/after-effects-cep-plugin/CSXS/manifest.xml +3 -3
- 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/ai/caption-authoring.d.ts +95 -0
- package/dist/ai/caption-authoring.js +213 -0
- package/dist/bridge/script-builder.js +65 -8
- package/dist/platform-capabilities.d.ts +1 -0
- package/dist/platform-capabilities.js +1 -0
- package/dist/tools/caption-authoring.d.ts +162 -5
- package/dist/tools/caption-authoring.js +81 -28
- package/dist/tools/health.d.ts +1 -0
- package/dist/tools/timeline.js +6 -4
- package/docs/supported-actions.md +1 -1
- package/package.json +1 -1
- package/public-product-manifest.json +1 -1
- package/uxp-plugin/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.24.0] - 2026-10-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `get_caption_style_guidance` accepts an optional, bounded `custom_style` (font family/weight, font size 8-400 px, `#RRGGBB` fill, up to three strokes with width and outer/inner/center placement, drop shadow, one of nine zones, X/Y offsets, Track Style name) and returns that spec with numbered Premiere UI steps carrying the exact values. Each extra stroke is added with + next to Stroke, and the look is saved via Track Style > + Create style. Preset steps use the same workflow. Results add `track_style` (whole-track scope, reuse from the Track Style dropdown in other sequences, project item in the Project panel), `limitations`, `warnings` (for example, layered outer strokes that do not grow wider) and `applied: false`. It is still guidance only: Premiere exposes no caption style API, so nothing is applied. Verify the look with `export_frame` or `export_sequence_review_frames`. Calls that pass only `preset` keep their `style` fields.
|
|
14
|
+
- The platform capability report lists `captionTrackStyle` under the CEP `unsupported` map, so clients see that caption track styling cannot be automated and are pointed to `get_caption_style_guidance`.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `__setItemMarks` now writes the same quarter-grid-biased seconds when applying a temporary overwrite range. Premiere 26.5.2 can floor exact tick-boundary seconds one media frame early, so `unnest_sequence` (16-tick default tolerance) refused a valid nest, or `replace_clip` placed a clip one frame short after the original was already removed. `replace_clip` also pulls a placed end that overshoots the original span by less than one media frame back onto that span.
|
|
19
|
+
|
|
20
|
+
- `__itemMarksForRestore` now applies that same write bias when it returns DOM marks (no private metadata, or DOM already matching the private range). Those paths previously handed exact tick-boundary seconds to restore, so a normal clip could shrink one media frame after replace/unnest on 26.5.2.
|
|
21
|
+
|
|
9
22
|
## [1.23.0] - 2026-10-11
|
|
10
23
|
|
|
11
24
|
### Added
|
|
@@ -25,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
25
38
|
|
|
26
39
|
- `unnest_sequence` now refuses, before removing the nested clip, any inner item that also carries the other media type. Premiere 26.5.2's `Track.overwriteClip` places that media on the matching track and overwrites the clips there. Same guard as `replace_clip`. Unnest a nest of video-only or audio-only items, or use Premiere's Unnest command.
|
|
27
40
|
- Shared project-item mark restore after `replace_clip`, `unnest_sequence`, and other `Track.overwriteClip` helpers now writes the quarter-frame-biased seconds from `__itemMarksForRestore`. Writing the exact tick boundary as seconds can floor one media frame early on Premiere 26.5.2 (a soft-subclip Out of `00:00:29:22` stored as `00:00:29:21`), shrinking the source item. `duplicate_clip` and `set_item_in_out` already used those seconds.
|
|
41
|
+
|
|
28
42
|
- `export_sequence` without `preset_path` no longer exports at proxy resolution when Media Encoder is not installed. Preset discovery only searched Premiere's `Settings/IngestPresets`, so the default became `IngestPresets/Proxy/00_1024x540 H.264.epr` and a 1080x1920 sequence was exported at 1024x540 while reporting `verified: true` (Premiere 24.0, macOS). Preset discovery now also searches Premiere's own `MediaIO/systempresets` when no Media Encoder presets exist, and the default never comes from `IngestPresets`; if no other H.264 preset is found, the tool asks for `preset_path`.
|
|
29
43
|
|
|
30
44
|
### Compatibility and evidence
|
package/README.md
CHANGED
|
@@ -129,13 +129,13 @@ The current source exposes 434 core tools for supported workflow steps spanning
|
|
|
129
129
|
|
|
130
130
|
<a id="latest-release"></a>
|
|
131
131
|
|
|
132
|
-
### Latest release: 1.
|
|
132
|
+
### Latest release: 1.24.0
|
|
133
133
|
|
|
134
|
-
The published v1.
|
|
135
|
-
and
|
|
134
|
+
The published v1.24.0 npm artifact contains **434 core tools**, 432 in its default profile,
|
|
135
|
+
and 528 with a compatible UXP connection. The development catalog above can include
|
|
136
136
|
unreleased work. See the [versioned facts and package provenance](https://premiere-pro-mcp.com/facts/).
|
|
137
137
|
|
|
138
|
-
GitHub releases use `v<SemVer>` for both title and tag, such as `v1.
|
|
138
|
+
GitHub releases use `v<SemVer>` for both title and tag, such as `v1.24.0`.
|
|
139
139
|
See [release naming and version conventions](docs/release-conventions.md).
|
|
140
140
|
|
|
141
141
|
### Try a bounded workflow
|
|
@@ -154,6 +154,12 @@ if the connection is unavailable.
|
|
|
154
154
|
|
|
155
155
|
### Release highlights
|
|
156
156
|
|
|
157
|
+
- **1.24.0 mark-write fixes:** temporary overwrite marks and restored project-item marks are written a quarter media frame past the tick boundary, so Premiere 26.5.2 no longer floors them one frame early in `unnest_sequence`, `replace_clip` and `duplicate_clip`.
|
|
158
|
+
- **1.24.0 caption style guidance:** `get_caption_style_guidance` accepts a bounded `custom_style` and returns Properties panel and Track Style steps; it is guidance only and applies nothing.
|
|
159
|
+
- **1.23.0 bounded workflows:** 41 added core tools and five guided routes cover exact timeline planning, editorial review, interchange/turnover and guarded delivery. The [52-item implementation ledger](docs/filmcraft-improvements.md) records each supported subset and host limit.
|
|
160
|
+
- **1.23.0 execution and recovery:** cooperating processes serialize protected edits, retain recovery for uncertain outcomes and report cancellation/partial work explicitly. UXP writes require the matching updated panel; older unversioned panels remain read-only.
|
|
161
|
+
- **1.23.0 artifact evidence:** review and delivery checks pin source checksums, inspect actual decoded frames/audio and preserve unknown native-job completion. Local decoder checks do not establish Premiere playback or native round-trip compatibility.
|
|
162
|
+
- **1.23.0 import fixes:** folder imports verify actual project-item readback; caption imports validate formats and counts and preserve mutation readback. Export preset discovery avoids proxy ingest presets, and unnesting refuses mixed-media overwrite hazards.
|
|
157
163
|
- **1.22.0 readback receipts:** effect writes, stabilization, smart bins, proxies, folder imports, sequence creation, marker deletion and clip selection report what Premiere applied as `verified`, `committed_unverified` or `failed`; `create_subclip`, `add_title` and `update_marker` report the stored values; `set_sequence_frame_rate` lists clips Premiere re-snapped.
|
|
158
164
|
- **1.22.0 display codes and proxies:** `set_sequence_display_format` writes Premiere's real display codes (video 100-113, audio 200-201) and maps the older 0-11 and 0-1 inputs onto them; `manage_proxies` toggle can set a known proxy display state; the Windows ACL check waits up to 30 seconds for PowerShell.
|
|
159
165
|
- **1.21.0 Claude Desktop startup fix:** the extension starts when optional settings are blank, and Desktop's discover-only probe no longer holds the UXP port, so the real server keeps its UXP tools.
|
|
@@ -195,7 +201,7 @@ if the connection is unavailable.
|
|
|
195
201
|
local Premiere processes. See the generated [supported action catalog](docs/supported-actions.md)
|
|
196
202
|
for individual capability and verification contracts.
|
|
197
203
|
|
|
198
|
-
See the [v1.
|
|
204
|
+
See the [v1.24.0 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.24.0)
|
|
199
205
|
for complete details. Live installation in Premiere Pro still requires host verification.
|
|
200
206
|
|
|
201
207
|
### Current MCP protocol support
|
|
@@ -253,7 +259,7 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
253
259
|
> ### Install the published package (verify the name)
|
|
254
260
|
>
|
|
255
261
|
> ```bash
|
|
256
|
-
> npm i -g premiere-pro-mcp@1.
|
|
262
|
+
> npm i -g premiere-pro-mcp@1.24.0
|
|
257
263
|
> ```
|
|
258
264
|
>
|
|
259
265
|
> 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:
|
|
@@ -261,7 +267,7 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
261
267
|
> | Check | Expected |
|
|
262
268
|
> | --- | --- |
|
|
263
269
|
> | Package name | `premiere-pro-mcp` (not `adobe-premiere-pro-mcp`) |
|
|
264
|
-
> | Version | `1.
|
|
270
|
+
> | Version | `1.24.0` |
|
|
265
271
|
> | Homepage / repo | https://premiere-pro-mcp.com/ · https://github.com/leancoderkavy/premiere-pro-mcp |
|
|
266
272
|
>
|
|
267
273
|
> ```bash
|
|
@@ -274,9 +280,9 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
274
280
|
|
|
275
281
|
### Easiest supported path: Claude Desktop
|
|
276
282
|
|
|
277
|
-
1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.
|
|
283
|
+
1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.24.0/premiere-pro-mcp-1.24.0.mcpb).
|
|
278
284
|
2. In Claude Desktop, open **Settings > Extensions > Advanced settings > Install Extension**, select the downloaded bundle, and restart Claude Desktop.
|
|
279
|
-
3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.
|
|
285
|
+
3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.24.0/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.
|
|
280
286
|
4. Restart Premiere, open a project, then open **Window > Extensions > MCP for Adobe Premiere Pro**.
|
|
281
287
|
5. In Claude, enter: `Safely check my Premiere connection with verify_premiere_connection. Make no changes.`
|
|
282
288
|
|
|
@@ -580,11 +586,11 @@ From a clone of this repository:
|
|
|
580
586
|
```bash
|
|
581
587
|
codex plugin marketplace add .
|
|
582
588
|
codex plugin add premiere-pro@premiere-pro-mcp
|
|
583
|
-
npx -y premiere-pro-mcp@1.
|
|
589
|
+
npx -y premiere-pro-mcp@1.24.0 --install-cep
|
|
584
590
|
```
|
|
585
591
|
|
|
586
592
|
Restart Premiere Pro and start a new Codex session after installation. The plugin
|
|
587
|
-
launches `premiere-pro-mcp@1.
|
|
593
|
+
launches `premiere-pro-mcp@1.24.0` through `npx`; the separate CEP installation is
|
|
588
594
|
required because the MCP server communicates with the running Premiere host through
|
|
589
595
|
the local bridge.
|
|
590
596
|
|
|
@@ -622,7 +628,7 @@ For Claude Code, add this repository as a marketplace and install the plugin:
|
|
|
622
628
|
Then install the Premiere bridge and start a new Claude Code session:
|
|
623
629
|
|
|
624
630
|
```bash
|
|
625
|
-
npx -y premiere-pro-mcp@1.
|
|
631
|
+
npx -y premiere-pro-mcp@1.24.0 --install-cep
|
|
626
632
|
```
|
|
627
633
|
|
|
628
634
|
The Claude Code package lives in
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.24.0" ExtensionBundleName="MCP for Adobe After Effects">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.
|
|
5
|
-
<Extension Id="com.mcp.aftereffects.bridge.headless" Version="1.
|
|
4
|
+
<Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.24.0"/>
|
|
5
|
+
<Extension Id="com.mcp.aftereffects.bridge.headless" Version="1.24.0"/>
|
|
6
6
|
</ExtensionList>
|
|
7
7
|
<ExecutionEnvironment>
|
|
8
8
|
<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.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.24.0" ExtensionBundleName="MCP for Adobe Premiere Pro">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.
|
|
5
|
-
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.
|
|
4
|
+
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.24.0"/>
|
|
5
|
+
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.24.0"/>
|
|
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.
|
|
84
|
+
<strong id="updateTitle">Version 1.24.0</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
|
@@ -103,6 +103,101 @@ type PlatformProfile = {
|
|
|
103
103
|
/** Approximate platform UI occlusion zones as normalized rects (x, y top-left). Verify against current platform overlay guides. */
|
|
104
104
|
export declare const PLATFORM_SAFE_ZONES: Record<SafeZonePlatform, PlatformProfile>;
|
|
105
105
|
export declare function describeCaptionStyle(preset: CaptionStylePreset): CaptionStyleDescriptor;
|
|
106
|
+
export declare const CAPTION_ZONES: readonly ["top_left", "top_center", "top_right", "middle_left", "middle_center", "middle_right", "bottom_left", "bottom_center", "bottom_right"];
|
|
107
|
+
export type CaptionZone = (typeof CAPTION_ZONES)[number];
|
|
108
|
+
export declare const CAPTION_STROKE_PLACEMENTS: readonly ["outer", "inner", "center"];
|
|
109
|
+
export type CaptionStrokePlacement = (typeof CAPTION_STROKE_PLACEMENTS)[number];
|
|
110
|
+
export declare const MAX_CAPTION_STROKES = 3;
|
|
111
|
+
export declare const CUSTOM_CAPTION_STYLE_LIMITS: {
|
|
112
|
+
readonly font_family_max_length: 128;
|
|
113
|
+
readonly font_weight_max_length: 64;
|
|
114
|
+
readonly track_style_name_max_length: 80;
|
|
115
|
+
readonly font_size_px: {
|
|
116
|
+
readonly min: 8;
|
|
117
|
+
readonly max: 400;
|
|
118
|
+
};
|
|
119
|
+
readonly stroke_width_px: {
|
|
120
|
+
readonly min: 0;
|
|
121
|
+
readonly max: 100;
|
|
122
|
+
};
|
|
123
|
+
readonly offset_px: {
|
|
124
|
+
readonly min: -2000;
|
|
125
|
+
readonly max: 2000;
|
|
126
|
+
};
|
|
127
|
+
readonly shadow: {
|
|
128
|
+
readonly opacity: {
|
|
129
|
+
readonly min: 0;
|
|
130
|
+
readonly max: 100;
|
|
131
|
+
};
|
|
132
|
+
readonly angle: {
|
|
133
|
+
readonly min: 0;
|
|
134
|
+
readonly max: 360;
|
|
135
|
+
};
|
|
136
|
+
readonly distance: {
|
|
137
|
+
readonly min: 0;
|
|
138
|
+
readonly max: 200;
|
|
139
|
+
};
|
|
140
|
+
readonly size: {
|
|
141
|
+
readonly min: 0;
|
|
142
|
+
readonly max: 100;
|
|
143
|
+
};
|
|
144
|
+
readonly blur: {
|
|
145
|
+
readonly min: 0;
|
|
146
|
+
readonly max: 100;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
export declare const CAPTION_SHADOW_DEFAULTS: {
|
|
151
|
+
readonly color: "#000000";
|
|
152
|
+
readonly opacity: 75;
|
|
153
|
+
readonly angle: 135;
|
|
154
|
+
readonly distance: 7;
|
|
155
|
+
readonly size: 0;
|
|
156
|
+
readonly blur: 40;
|
|
157
|
+
};
|
|
158
|
+
export declare const DEFAULT_TRACK_STYLE_NAME = "Caption Track Style";
|
|
159
|
+
export type CaptionStroke = {
|
|
160
|
+
color: string;
|
|
161
|
+
width_px: number;
|
|
162
|
+
placement: CaptionStrokePlacement;
|
|
163
|
+
};
|
|
164
|
+
export type CaptionShadow = {
|
|
165
|
+
color: string;
|
|
166
|
+
opacity: number;
|
|
167
|
+
angle: number;
|
|
168
|
+
distance: number;
|
|
169
|
+
size: number;
|
|
170
|
+
blur: number;
|
|
171
|
+
};
|
|
172
|
+
export type CustomCaptionStyle = {
|
|
173
|
+
font_family?: string;
|
|
174
|
+
font_weight?: string;
|
|
175
|
+
font_size_px?: number;
|
|
176
|
+
fill_color?: string;
|
|
177
|
+
strokes?: CaptionStroke[];
|
|
178
|
+
shadow?: CaptionShadow;
|
|
179
|
+
zone?: CaptionZone;
|
|
180
|
+
offset_x_px?: number;
|
|
181
|
+
offset_y_px?: number;
|
|
182
|
+
track_style_name?: string;
|
|
183
|
+
};
|
|
184
|
+
export declare function validateCustomCaptionStyle(value: unknown): CustomCaptionStyle;
|
|
185
|
+
/** Warnings about a custom style that Premiere will accept but that may not render as intended. */
|
|
186
|
+
export declare function customCaptionStyleWarnings(style: CustomCaptionStyle): string[];
|
|
187
|
+
/** Premiere 2024+ Properties-panel / Track Style steps for a custom caption style. */
|
|
188
|
+
export declare function buildCustomCaptionStyleSteps(style: CustomCaptionStyle, preset?: CaptionStylePreset): string[];
|
|
189
|
+
/** Premiere 2024+ Properties-panel / Track Style steps for a built-in preset. */
|
|
190
|
+
export declare function buildPresetCaptionStyleSteps(style: CaptionStyleDescriptor): string[];
|
|
191
|
+
export declare const CAPTION_TRACK_STYLE_GUIDANCE: {
|
|
192
|
+
readonly scope: "A Track Style applies to every caption on the caption track. Style one caption, save it with Track Style > + Create style, and the whole track updates; no multi-select is needed.";
|
|
193
|
+
readonly reusable: "A saved Track Style can be applied to the caption track of any other sequence in the project from the Track Style dropdown.";
|
|
194
|
+
readonly project_item: "Each Track Style is stored as a project item in the Project panel; keep it in a shared bin so it can be found and reused.";
|
|
195
|
+
readonly limitations: readonly ["Premiere Pro exposes no documented ExtendScript, QE DOM, or UXP API that creates, edits, or applies caption styles, so these steps must be performed in the Premiere UI.", "These steps target Premiere Pro 2024 and later. Earlier versions lack the Track Style workflow described here and were not checked; panel labels can also differ between versions and UI languages.", "Verify the rendered result with export_frame or export_sequence_review_frames; this tool cannot read the applied style back."];
|
|
196
|
+
};
|
|
197
|
+
export declare function captionSafeZoneRecommendation(): Record<SafeZonePlatform, {
|
|
198
|
+
x: number;
|
|
199
|
+
y: number;
|
|
200
|
+
}>;
|
|
106
201
|
export declare function formatCaptionTimestamp(ms: number, format: CaptionFormat): string;
|
|
107
202
|
/** Greedy word wrap that never splits a word; a single word longer than the width occupies its own line. */
|
|
108
203
|
export declare function wrapCaptionWords(words: readonly string[], maxChars: number): string[][];
|
|
@@ -154,6 +154,219 @@ export function describeCaptionStyle(preset) {
|
|
|
154
154
|
note: "Documentation only. SRT/VTT artifacts carry timing and text; apply visual styling in Premiere's caption track style after import.",
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Custom caption track style (documentation only)
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
export const CAPTION_ZONES = ["top_left", "top_center", "top_right", "middle_left", "middle_center", "middle_right", "bottom_left", "bottom_center", "bottom_right"];
|
|
161
|
+
export const CAPTION_STROKE_PLACEMENTS = ["outer", "inner", "center"];
|
|
162
|
+
export const MAX_CAPTION_STROKES = 3;
|
|
163
|
+
export const CUSTOM_CAPTION_STYLE_LIMITS = {
|
|
164
|
+
font_family_max_length: 128,
|
|
165
|
+
font_weight_max_length: 64,
|
|
166
|
+
track_style_name_max_length: 80,
|
|
167
|
+
font_size_px: { min: 8, max: 400 },
|
|
168
|
+
stroke_width_px: { min: 0, max: 100 },
|
|
169
|
+
offset_px: { min: -2000, max: 2000 },
|
|
170
|
+
shadow: {
|
|
171
|
+
opacity: { min: 0, max: 100 },
|
|
172
|
+
angle: { min: 0, max: 360 },
|
|
173
|
+
distance: { min: 0, max: 200 },
|
|
174
|
+
size: { min: 0, max: 100 },
|
|
175
|
+
blur: { min: 0, max: 100 },
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
export const CAPTION_SHADOW_DEFAULTS = { color: "#000000", opacity: 75, angle: 135, distance: 7, size: 0, blur: 40 };
|
|
179
|
+
export const DEFAULT_TRACK_STYLE_NAME = "Caption Track Style";
|
|
180
|
+
const CUSTOM_STYLE_KEYS = ["font_family", "font_weight", "font_size_px", "fill_color", "strokes", "shadow", "zone", "offset_x_px", "offset_y_px", "track_style_name"];
|
|
181
|
+
const STROKE_KEYS = ["color", "width_px", "placement"];
|
|
182
|
+
const SHADOW_KEYS = ["color", "opacity", "angle", "distance", "size", "blur"];
|
|
183
|
+
const HEX_COLOR = /^#[0-9A-Fa-f]{6}$/;
|
|
184
|
+
function plainObject(value, label, allowed) {
|
|
185
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
186
|
+
fail(`${label} must be an object`);
|
|
187
|
+
const record = value;
|
|
188
|
+
const unknown = Object.keys(record).find((key) => !allowed.includes(key));
|
|
189
|
+
if (unknown)
|
|
190
|
+
fail(`${label} has an unknown field: ${unknown}`);
|
|
191
|
+
return record;
|
|
192
|
+
}
|
|
193
|
+
function boundedText(value, label, maxLength) {
|
|
194
|
+
if (typeof value !== "string" || !value.trim() || value.length > maxLength)
|
|
195
|
+
fail(`${label} must be a non-empty string of at most ${maxLength} characters`);
|
|
196
|
+
return value.trim();
|
|
197
|
+
}
|
|
198
|
+
function hexColor(value, label) {
|
|
199
|
+
if (typeof value !== "string" || !HEX_COLOR.test(value))
|
|
200
|
+
fail(`${label} must be a hex colour in the form #RRGGBB`);
|
|
201
|
+
return value.toUpperCase();
|
|
202
|
+
}
|
|
203
|
+
function rangedNumber(value, label, range, integer = false) {
|
|
204
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
205
|
+
fail(`${label} must be a finite number`);
|
|
206
|
+
if (integer && !Number.isInteger(value))
|
|
207
|
+
fail(`${label} must be an integer`);
|
|
208
|
+
if (value < range.min || value > range.max)
|
|
209
|
+
fail(`${label} must be between ${range.min} and ${range.max}`);
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
export function validateCustomCaptionStyle(value) {
|
|
213
|
+
const raw = plainObject(value, "custom_style", CUSTOM_STYLE_KEYS);
|
|
214
|
+
if (!Object.keys(raw).length)
|
|
215
|
+
fail("custom_style must set at least one field");
|
|
216
|
+
const limits = CUSTOM_CAPTION_STYLE_LIMITS;
|
|
217
|
+
const style = {};
|
|
218
|
+
if (raw.font_family !== undefined)
|
|
219
|
+
style.font_family = boundedText(raw.font_family, "custom_style.font_family", limits.font_family_max_length);
|
|
220
|
+
if (raw.font_weight !== undefined)
|
|
221
|
+
style.font_weight = boundedText(raw.font_weight, "custom_style.font_weight", limits.font_weight_max_length);
|
|
222
|
+
if (raw.font_size_px !== undefined)
|
|
223
|
+
style.font_size_px = rangedNumber(raw.font_size_px, "custom_style.font_size_px", limits.font_size_px, true);
|
|
224
|
+
if (raw.fill_color !== undefined)
|
|
225
|
+
style.fill_color = hexColor(raw.fill_color, "custom_style.fill_color");
|
|
226
|
+
if (raw.strokes !== undefined) {
|
|
227
|
+
if (!Array.isArray(raw.strokes) || raw.strokes.length > MAX_CAPTION_STROKES)
|
|
228
|
+
fail(`custom_style.strokes must be an array of at most ${MAX_CAPTION_STROKES} strokes`);
|
|
229
|
+
style.strokes = raw.strokes.map((item, index) => {
|
|
230
|
+
const label = `custom_style.strokes[${index}]`;
|
|
231
|
+
const stroke = plainObject(item, label, STROKE_KEYS);
|
|
232
|
+
const placement = stroke.placement === undefined ? "outer" : stroke.placement;
|
|
233
|
+
if (typeof placement !== "string" || !CAPTION_STROKE_PLACEMENTS.includes(placement))
|
|
234
|
+
fail(`${label}.placement must be one of: ${CAPTION_STROKE_PLACEMENTS.join(", ")}`);
|
|
235
|
+
return {
|
|
236
|
+
color: hexColor(stroke.color, `${label}.color`),
|
|
237
|
+
width_px: rangedNumber(stroke.width_px, `${label}.width_px`, limits.stroke_width_px),
|
|
238
|
+
placement: placement,
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (raw.shadow !== undefined) {
|
|
243
|
+
const shadow = plainObject(raw.shadow, "custom_style.shadow", SHADOW_KEYS);
|
|
244
|
+
const numberOr = (key) => shadow[key] === undefined ? CAPTION_SHADOW_DEFAULTS[key] : rangedNumber(shadow[key], `custom_style.shadow.${key}`, limits.shadow[key]);
|
|
245
|
+
style.shadow = {
|
|
246
|
+
color: shadow.color === undefined ? CAPTION_SHADOW_DEFAULTS.color : hexColor(shadow.color, "custom_style.shadow.color"),
|
|
247
|
+
opacity: numberOr("opacity"),
|
|
248
|
+
angle: numberOr("angle"),
|
|
249
|
+
distance: numberOr("distance"),
|
|
250
|
+
size: numberOr("size"),
|
|
251
|
+
blur: numberOr("blur"),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (raw.zone !== undefined) {
|
|
255
|
+
if (typeof raw.zone !== "string" || !CAPTION_ZONES.includes(raw.zone))
|
|
256
|
+
fail(`custom_style.zone must be one of: ${CAPTION_ZONES.join(", ")}`);
|
|
257
|
+
style.zone = raw.zone;
|
|
258
|
+
}
|
|
259
|
+
if (raw.offset_x_px !== undefined)
|
|
260
|
+
style.offset_x_px = rangedNumber(raw.offset_x_px, "custom_style.offset_x_px", limits.offset_px, true);
|
|
261
|
+
if (raw.offset_y_px !== undefined)
|
|
262
|
+
style.offset_y_px = rangedNumber(raw.offset_y_px, "custom_style.offset_y_px", limits.offset_px, true);
|
|
263
|
+
if (raw.track_style_name !== undefined)
|
|
264
|
+
style.track_style_name = boundedText(raw.track_style_name, "custom_style.track_style_name", limits.track_style_name_max_length);
|
|
265
|
+
return style;
|
|
266
|
+
}
|
|
267
|
+
const ordinal = (index) => ["first", "second", "third"][index] ?? `#${index + 1}`;
|
|
268
|
+
const zoneLabel = (zone) => zone.replace("_", "-");
|
|
269
|
+
/** Warnings about a custom style that Premiere will accept but that may not render as intended. */
|
|
270
|
+
export function customCaptionStyleWarnings(style) {
|
|
271
|
+
const warnings = [];
|
|
272
|
+
const outer = (style.strokes ?? []).filter((stroke) => stroke.placement === "outer");
|
|
273
|
+
for (let index = 1; index < outer.length; index += 1) {
|
|
274
|
+
if (outer[index].width_px <= outer[index - 1].width_px)
|
|
275
|
+
warnings.push(`outer stroke ${index + 1} (${outer[index].width_px} px) is not wider than outer stroke ${index} (${outer[index - 1].width_px} px), so it may be hidden behind it; layered outer strokes need increasing widths`);
|
|
276
|
+
}
|
|
277
|
+
if (style.shadow && style.shadow.opacity === 0)
|
|
278
|
+
warnings.push("shadow opacity is 0, so the shadow will not be visible");
|
|
279
|
+
if (style.strokes?.some((stroke) => stroke.width_px === 0))
|
|
280
|
+
warnings.push("a stroke with width 0 will not be visible");
|
|
281
|
+
return warnings;
|
|
282
|
+
}
|
|
283
|
+
const TRACK_STYLE_SELECT_STEPS = [
|
|
284
|
+
"Select one caption on the caption track in the timeline. One caption is enough: a Track Style applies to every caption on the track, so no multi-select is needed.",
|
|
285
|
+
"Open the Properties panel (Window > Properties). If your Premiere version has no Properties panel, use Window > Essential Graphics > Edit, which shows the same caption sections.",
|
|
286
|
+
];
|
|
287
|
+
function trackStyleSaveSteps(name) {
|
|
288
|
+
return [
|
|
289
|
+
`Track Style: open the Track Style dropdown in the Properties panel, choose "+ Create style", and name it "${name}". The style now applies to the whole caption track.`,
|
|
290
|
+
"Scrub through the track and confirm that the other captions show the new style.",
|
|
291
|
+
`Reuse: select a caption on the caption track in another sequence and pick "${name}" from the Track Style dropdown.`,
|
|
292
|
+
`The style is a project item in the Project panel. Move "${name}" into a shared bin (for example "Caption Styles") so other sequences and projects can reuse it.`,
|
|
293
|
+
"Verify the rendered look with export_frame or export_sequence_review_frames.",
|
|
294
|
+
];
|
|
295
|
+
}
|
|
296
|
+
const numbered = (steps) => steps.map((step, index) => `${index + 1}. ${step}`);
|
|
297
|
+
/** Premiere 2024+ Properties-panel / Track Style steps for a custom caption style. */
|
|
298
|
+
export function buildCustomCaptionStyleSteps(style, preset) {
|
|
299
|
+
const fallback = preset ? describeCaptionStyle(preset) : undefined;
|
|
300
|
+
const steps = [...TRACK_STYLE_SELECT_STEPS];
|
|
301
|
+
const text = [];
|
|
302
|
+
const family = style.font_family ?? fallback?.font_family_suggestion;
|
|
303
|
+
const weight = style.font_weight ?? fallback?.font_weight;
|
|
304
|
+
if (family)
|
|
305
|
+
text.push(`font family "${family}"`);
|
|
306
|
+
if (weight)
|
|
307
|
+
text.push(`font weight/style "${weight}"`);
|
|
308
|
+
if (style.font_size_px !== undefined)
|
|
309
|
+
text.push(`font size ${style.font_size_px}`);
|
|
310
|
+
else if (fallback)
|
|
311
|
+
text.push(`font size about ${fallback.font_size_percent_of_height}% of the frame height`);
|
|
312
|
+
if (text.length)
|
|
313
|
+
steps.push(`Text: set ${text.join(", ")}.`);
|
|
314
|
+
if (style.fill_color)
|
|
315
|
+
steps.push(`Appearance > Fill: turn Fill on and set the colour to ${style.fill_color}.`);
|
|
316
|
+
if (style.strokes) {
|
|
317
|
+
if (!style.strokes.length)
|
|
318
|
+
steps.push("Appearance > Stroke: turn Stroke off.");
|
|
319
|
+
style.strokes.forEach((stroke, index) => {
|
|
320
|
+
const values = `colour ${stroke.color}, width ${stroke.width_px}, ${stroke.placement} stroke`;
|
|
321
|
+
steps.push(index === 0
|
|
322
|
+
? `Appearance > Stroke: turn Stroke on and set the ${ordinal(index)} stroke to ${values}.`
|
|
323
|
+
: `Appearance > Stroke: press + next to Stroke to add stroke ${index + 1}, then set it to ${values}.`);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
else if (fallback) {
|
|
327
|
+
steps.push(`Appearance > Stroke: ${fallback.stroke ? "turn Stroke on" : "leave Stroke off"} (preset ${fallback.preset}).`);
|
|
328
|
+
}
|
|
329
|
+
if (style.shadow) {
|
|
330
|
+
const shadow = style.shadow;
|
|
331
|
+
steps.push(`Appearance > Shadow: turn Shadow on and set colour ${shadow.color}, Opacity ${shadow.opacity}%, Angle ${shadow.angle}°, Distance ${shadow.distance}, Size ${shadow.size}, Blur ${shadow.blur}.`);
|
|
332
|
+
}
|
|
333
|
+
if (style.zone)
|
|
334
|
+
steps.push(`Align and Transform: choose the ${zoneLabel(style.zone)} cell in the caption region (zone) grid.`);
|
|
335
|
+
else if (fallback)
|
|
336
|
+
steps.push(`Align and Transform: place the caption centre near x=${fallback.position.x * 100}%, y=${fallback.position.y * 100}% of the frame (preset ${fallback.preset}).`);
|
|
337
|
+
if (style.offset_x_px !== undefined || style.offset_y_px !== undefined) {
|
|
338
|
+
steps.push(`Align and Transform: set the position offset to X ${style.offset_x_px ?? 0} and Y ${style.offset_y_px ?? 0} (pixels relative to the chosen zone; negative Y moves the caption up).`);
|
|
339
|
+
}
|
|
340
|
+
steps.push(...trackStyleSaveSteps(style.track_style_name ?? DEFAULT_TRACK_STYLE_NAME));
|
|
341
|
+
return numbered(steps);
|
|
342
|
+
}
|
|
343
|
+
/** Premiere 2024+ Properties-panel / Track Style steps for a built-in preset. */
|
|
344
|
+
export function buildPresetCaptionStyleSteps(style) {
|
|
345
|
+
const steps = [
|
|
346
|
+
...TRACK_STYLE_SELECT_STEPS,
|
|
347
|
+
`Text: set font family "${style.font_family_suggestion}", font weight ${style.font_weight}, and a font size of about ${style.font_size_percent_of_height}% of the frame height.`,
|
|
348
|
+
`Appearance > Stroke: ${style.stroke ? "turn Stroke on (press + next to Stroke for a second, wider outer stroke if you want a layered outline)" : "leave Stroke off"}.`,
|
|
349
|
+
`Appearance > Background: ${style.background ? "turn Background on and lower its opacity so the video stays visible" : "leave Background off"}.`,
|
|
350
|
+
`Align and Transform: centre-align the text and place the caption centre near x=${style.position.x * 100}%, y=${style.position.y * 100}% of the frame (normalized, centre-anchored).`,
|
|
351
|
+
...(style.word_highlight ? ["Word highlight: enable word highlighting for a karaoke-style progressive reveal where your Premiere version offers it."] : []),
|
|
352
|
+
...(style.uppercase_recommended ? ["Text: uppercase is recommended for this preset (use the All Caps option or the build_caption_artifact uppercase flag)."] : []),
|
|
353
|
+
...trackStyleSaveSteps(`${style.preset} captions`),
|
|
354
|
+
];
|
|
355
|
+
return numbered(steps);
|
|
356
|
+
}
|
|
357
|
+
export const CAPTION_TRACK_STYLE_GUIDANCE = {
|
|
358
|
+
scope: "A Track Style applies to every caption on the caption track. Style one caption, save it with Track Style > + Create style, and the whole track updates; no multi-select is needed.",
|
|
359
|
+
reusable: "A saved Track Style can be applied to the caption track of any other sequence in the project from the Track Style dropdown.",
|
|
360
|
+
project_item: "Each Track Style is stored as a project item in the Project panel; keep it in a shared bin so it can be found and reused.",
|
|
361
|
+
limitations: [
|
|
362
|
+
"Premiere Pro exposes no documented ExtendScript, QE DOM, or UXP API that creates, edits, or applies caption styles, so these steps must be performed in the Premiere UI.",
|
|
363
|
+
"These steps target Premiere Pro 2024 and later. Earlier versions lack the Track Style workflow described here and were not checked; panel labels can also differ between versions and UI languages.",
|
|
364
|
+
"Verify the rendered result with export_frame or export_sequence_review_frames; this tool cannot read the applied style back.",
|
|
365
|
+
],
|
|
366
|
+
};
|
|
367
|
+
export function captionSafeZoneRecommendation() {
|
|
368
|
+
return safeZoneRecommendation();
|
|
369
|
+
}
|
|
157
370
|
const toMs = (seconds) => Math.round(seconds * 1000);
|
|
158
371
|
const msToSeconds = (ms) => Number((ms / 1000).toFixed(3));
|
|
159
372
|
function pad(value, width) {
|
|
@@ -118,6 +118,33 @@ function __isTrackLocked(track) {
|
|
|
118
118
|
return false;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// Seconds used to put marks back. Exact tick-boundary seconds can floor one
|
|
122
|
+
// media frame early on 26.5.2; write a quarter grid past the boundary.
|
|
123
|
+
function __restoreSecondsFromTicks(ticks, gridTicks) {
|
|
124
|
+
var grid = parseFloat(gridTicks);
|
|
125
|
+
if (!isFinite(grid) || !(grid > 0)) grid = TICKS_PER_SECOND / 24;
|
|
126
|
+
return (parseFloat(ticks) + grid / 4) / TICKS_PER_SECOND;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function __restoreGridFromItem(item, mediaType) {
|
|
130
|
+
if (mediaType === 2) return TICKS_PER_SECOND / 48000;
|
|
131
|
+
try {
|
|
132
|
+
var fps = parseFloat(item.getFootageInterpretation().frameRate);
|
|
133
|
+
if (fps > 0) return TICKS_PER_SECOND / fps;
|
|
134
|
+
} catch (eGrid) {}
|
|
135
|
+
return TICKS_PER_SECOND / 24;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function __biasedDomMarks(marks, gridTicks) {
|
|
139
|
+
if (!marks) return marks;
|
|
140
|
+
return {
|
|
141
|
+
inTicks: marks.inTicks,
|
|
142
|
+
outTicks: marks.outTicks,
|
|
143
|
+
inSeconds: __restoreSecondsFromTicks(marks.inTicks, gridTicks),
|
|
144
|
+
outSeconds: __restoreSecondsFromTicks(marks.outTicks, gridTicks)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
121
148
|
// Read the private source range before temporarily changing project-item marks.
|
|
122
149
|
function __itemMarksForRestore(item, mediaType) {
|
|
123
150
|
function domMarks() {
|
|
@@ -131,7 +158,7 @@ function __itemMarksForRestore(item, mediaType) {
|
|
|
131
158
|
}
|
|
132
159
|
var dom = domMarks(), xml = "";
|
|
133
160
|
try { if (typeof item.getProjectMetadata === "function") xml = String(item.getProjectMetadata() || ""); } catch (metadataMarkError) { return null; }
|
|
134
|
-
if (!xml) return dom;
|
|
161
|
+
if (!xml) return __biasedDomMarks(dom, __restoreGridFromItem(item, mediaType));
|
|
135
162
|
if (xml.length > 1000000) return null;
|
|
136
163
|
function field(name, text) {
|
|
137
164
|
var escaped = name.replace(/\\./g, "\\\\.");
|
|
@@ -192,7 +219,7 @@ function __itemMarksForRestore(item, mediaType) {
|
|
|
192
219
|
}
|
|
193
220
|
if (inBlock === null && outBlock === null) {
|
|
194
221
|
if (xml.indexOf("Column.Intrinsic." + kind + "InPoint") >= 0 || xml.indexOf("Column.Intrinsic." + kind + "OutPoint") >= 0) return null;
|
|
195
|
-
return dom;
|
|
222
|
+
return __biasedDomMarks(dom, __restoreGridFromItem(item, mediaType));
|
|
196
223
|
}
|
|
197
224
|
var left = point(inBlock, kind === "Audio"), right = point(outBlock, kind === "Audio");
|
|
198
225
|
// The private VideoOutPoint names the last frame (inclusive): a subclip made
|
|
@@ -200,7 +227,12 @@ function __itemMarksForRestore(item, mediaType) {
|
|
|
200
227
|
if (right && kind !== "Audio") right.ticks += right.frameTicks;
|
|
201
228
|
if (!left || !right || !isFinite(left.ticks) || !isFinite(right.ticks) || left.ticks < 0 || right.ticks > 9007199254740991 || right.ticks <= left.ticks || Math.abs(left.frameTicks - right.frameTicks) > 1) return null;
|
|
202
229
|
var tolerance = Math.max(left.frameTicks, right.frameTicks);
|
|
203
|
-
|
|
230
|
+
// DOM already matches the private range. Still bias the write seconds: 26.5.2
|
|
231
|
+
// floors exact tick-boundary seconds one media frame early, which shrinks a
|
|
232
|
+
// normal clip after replace/unnest the same way it shrinks a soft subclip.
|
|
233
|
+
if (dom && Math.abs(Number(dom.inTicks) - left.ticks) <= tolerance && Math.abs(Number(dom.outTicks) - right.ticks) <= tolerance) {
|
|
234
|
+
return __biasedDomMarks(dom, tolerance);
|
|
235
|
+
}
|
|
204
236
|
// Premiere floors a written mark to the media's frame (or sample) grid, so a
|
|
205
237
|
// value exactly on the boundary can land one frame early (live 26.5.2: a
|
|
206
238
|
// restored Out of 00:00:29:22 read back 00:00:29:21). Write a quarter frame
|
|
@@ -215,18 +247,43 @@ function __itemMarksForRestore(item, mediaType) {
|
|
|
215
247
|
// empty first; overwrite never shifts neighbours but would replace them.
|
|
216
248
|
// Returns { ok, attempted, marksRestored, error }. attempted=true means the
|
|
217
249
|
// overwrite call ran (or threw), so the timeline may have changed.
|
|
250
|
+
// Media/sample grid for a project-item mark write. Video uses footage fps;
|
|
251
|
+
// audio uses 48 kHz when the host does not expose a rate. Fallback 24 fps is
|
|
252
|
+
// smaller than one frame at 23.976–60 and still larger than float ulp.
|
|
253
|
+
function __mediaGridTicks(item, mediaType) {
|
|
254
|
+
if (mediaType !== 2) {
|
|
255
|
+
try {
|
|
256
|
+
var fps = parseFloat(item.getFootageInterpretation().frameRate);
|
|
257
|
+
if (fps > 0) return TICKS_PER_SECOND / fps;
|
|
258
|
+
} catch (eInterp) {}
|
|
259
|
+
return TICKS_PER_SECOND / 24;
|
|
260
|
+
}
|
|
261
|
+
return TICKS_PER_SECOND / 48000;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function __biasedMarkSeconds(ticks, gridTicks) {
|
|
265
|
+
var grid = parseFloat(gridTicks);
|
|
266
|
+
if (!isFinite(grid) || !(grid > 0)) grid = TICKS_PER_SECOND / 24;
|
|
267
|
+
return (parseFloat(ticks) + grid / 4) / TICKS_PER_SECOND;
|
|
268
|
+
}
|
|
269
|
+
|
|
218
270
|
// Set a project item's In/Out marks, ordering the writes so the In never
|
|
219
271
|
// passes the current Out, and report whether both read back within tolerance
|
|
220
|
-
// ticks (default: exact).
|
|
272
|
+
// ticks (default: exact). Write a quarter grid past the tick boundary: the
|
|
273
|
+
// same 26.5.2 floor that shrinks restored soft-subclip marks also shortens
|
|
274
|
+
// the temporary overwrite range (unnest/replace) when exact seconds are used.
|
|
221
275
|
function __setItemMarks(item, wantIn, wantOut, mediaType, tolerance) {
|
|
222
276
|
var tol = tolerance > __TICK_MATCH_TOL ? tolerance : __TICK_MATCH_TOL;
|
|
277
|
+
var grid = __mediaGridTicks(item, mediaType);
|
|
278
|
+
var inSeconds = __biasedMarkSeconds(wantIn, grid);
|
|
279
|
+
var outSeconds = __biasedMarkSeconds(wantOut, grid);
|
|
223
280
|
var currentOut = parseFloat(item.getOutPoint(mediaType).ticks);
|
|
224
281
|
if (parseFloat(wantIn) >= currentOut) {
|
|
225
|
-
item.setOutPoint(
|
|
226
|
-
item.setInPoint(
|
|
282
|
+
item.setOutPoint(outSeconds, mediaType);
|
|
283
|
+
item.setInPoint(inSeconds, mediaType);
|
|
227
284
|
} else {
|
|
228
|
-
item.setInPoint(
|
|
229
|
-
item.setOutPoint(
|
|
285
|
+
item.setInPoint(inSeconds, mediaType);
|
|
286
|
+
item.setOutPoint(outSeconds, mediaType);
|
|
230
287
|
}
|
|
231
288
|
return Math.abs(parseFloat(item.getInPoint(mediaType).ticks) - parseFloat(wantIn)) <= tol &&
|
|
232
289
|
Math.abs(parseFloat(item.getOutPoint(mediaType).ticks) - parseFloat(wantOut)) <= tol;
|
|
@@ -229,6 +229,7 @@ export function buildPlatformCapabilityReport(capabilities, platform = process.p
|
|
|
229
229
|
cloudPublish: "No documented Premiere automation API for publishing to cloud destinations.",
|
|
230
230
|
contentCredentials: "No documented Premiere automation API for configuring Content Credentials.",
|
|
231
231
|
renderAndReplace: "No documented Premiere ExtendScript or UXP Render and Replace API.",
|
|
232
|
+
captionTrackStyle: "No documented Premiere ExtendScript, QE DOM, or UXP API creates, edits, or applies caption track styles; call get_caption_style_guidance for Properties-panel and Track Style steps to apply in the Premiere UI.",
|
|
232
233
|
},
|
|
233
234
|
},
|
|
234
235
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CaptionStylePreset } from "../ai/caption-authoring.js";
|
|
1
2
|
export declare const MAX_INLINE_ARTIFACT_BYTES: number;
|
|
2
3
|
/**
|
|
3
4
|
* Resolves `output_path` to a writable location contained inside the approved
|
|
@@ -343,22 +344,178 @@ export declare function getCaptionAuthoringTools(): {
|
|
|
343
344
|
enum: ("podcast" | "clean" | "bold_pop" | "karaoke" | "lecture")[];
|
|
344
345
|
description: string;
|
|
345
346
|
};
|
|
347
|
+
custom_style: {
|
|
348
|
+
readonly type: "object";
|
|
349
|
+
readonly additionalProperties: false;
|
|
350
|
+
readonly description: "Optional caption look to translate into concrete Premiere UI steps. Every field is optional; at least one is required. Values are documentation only and are never applied.";
|
|
351
|
+
readonly properties: {
|
|
352
|
+
readonly font_family: {
|
|
353
|
+
readonly type: "string";
|
|
354
|
+
readonly minLength: 1;
|
|
355
|
+
readonly maxLength: 128;
|
|
356
|
+
readonly description: "Font family name as shown in Premiere, e.g. Source Han Sans TW.";
|
|
357
|
+
};
|
|
358
|
+
readonly font_weight: {
|
|
359
|
+
readonly type: "string";
|
|
360
|
+
readonly minLength: 1;
|
|
361
|
+
readonly maxLength: 64;
|
|
362
|
+
readonly description: "Font weight or style name, e.g. Heavy or Bold.";
|
|
363
|
+
};
|
|
364
|
+
readonly font_size_px: {
|
|
365
|
+
readonly type: "integer";
|
|
366
|
+
readonly minimum: 8;
|
|
367
|
+
readonly maximum: 400;
|
|
368
|
+
readonly description: "Font size in Premiere's caption units (pixels).";
|
|
369
|
+
};
|
|
370
|
+
readonly fill_color: {
|
|
371
|
+
readonly type: "string";
|
|
372
|
+
readonly pattern: "^#[0-9A-Fa-f]{6}$";
|
|
373
|
+
readonly description: "Text fill colour as #RRGGBB.";
|
|
374
|
+
};
|
|
375
|
+
readonly strokes: {
|
|
376
|
+
readonly type: "array";
|
|
377
|
+
readonly maxItems: 3;
|
|
378
|
+
readonly description: "Up to three strokes in Premiere's Stroke list order; each stroke after the first is added with + next to Stroke. Layered outer strokes need increasing widths.";
|
|
379
|
+
readonly items: {
|
|
380
|
+
readonly type: "object";
|
|
381
|
+
readonly additionalProperties: false;
|
|
382
|
+
readonly description: "One stroke.";
|
|
383
|
+
readonly properties: {
|
|
384
|
+
readonly color: {
|
|
385
|
+
readonly type: "string";
|
|
386
|
+
readonly pattern: "^#[0-9A-Fa-f]{6}$";
|
|
387
|
+
readonly description: "Stroke colour as #RRGGBB.";
|
|
388
|
+
};
|
|
389
|
+
readonly width_px: {
|
|
390
|
+
readonly type: "number";
|
|
391
|
+
readonly minimum: 0;
|
|
392
|
+
readonly maximum: 100;
|
|
393
|
+
readonly description: "Stroke width.";
|
|
394
|
+
};
|
|
395
|
+
readonly placement: {
|
|
396
|
+
readonly type: "string";
|
|
397
|
+
readonly enum: readonly ["outer", "inner", "center"];
|
|
398
|
+
readonly description: "Stroke placement (default outer).";
|
|
399
|
+
};
|
|
400
|
+
};
|
|
401
|
+
readonly required: readonly ["color", "width_px"];
|
|
402
|
+
};
|
|
403
|
+
};
|
|
404
|
+
readonly shadow: {
|
|
405
|
+
readonly type: "object";
|
|
406
|
+
readonly additionalProperties: false;
|
|
407
|
+
readonly description: "Drop shadow; omitted fields use the listed defaults.";
|
|
408
|
+
readonly properties: {
|
|
409
|
+
readonly color: {
|
|
410
|
+
readonly type: "string";
|
|
411
|
+
readonly pattern: "^#[0-9A-Fa-f]{6}$";
|
|
412
|
+
readonly description: "Shadow colour as #RRGGBB (default #000000).";
|
|
413
|
+
};
|
|
414
|
+
readonly opacity: {
|
|
415
|
+
type: string;
|
|
416
|
+
minimum: 0;
|
|
417
|
+
maximum: 100 | 200 | 360;
|
|
418
|
+
description: string;
|
|
419
|
+
};
|
|
420
|
+
readonly angle: {
|
|
421
|
+
type: string;
|
|
422
|
+
minimum: 0;
|
|
423
|
+
maximum: 100 | 200 | 360;
|
|
424
|
+
description: string;
|
|
425
|
+
};
|
|
426
|
+
readonly distance: {
|
|
427
|
+
type: string;
|
|
428
|
+
minimum: 0;
|
|
429
|
+
maximum: 100 | 200 | 360;
|
|
430
|
+
description: string;
|
|
431
|
+
};
|
|
432
|
+
readonly size: {
|
|
433
|
+
type: string;
|
|
434
|
+
minimum: 0;
|
|
435
|
+
maximum: 100 | 200 | 360;
|
|
436
|
+
description: string;
|
|
437
|
+
};
|
|
438
|
+
readonly blur: {
|
|
439
|
+
type: string;
|
|
440
|
+
minimum: 0;
|
|
441
|
+
maximum: 100 | 200 | 360;
|
|
442
|
+
description: string;
|
|
443
|
+
};
|
|
444
|
+
};
|
|
445
|
+
};
|
|
446
|
+
readonly zone: {
|
|
447
|
+
readonly type: "string";
|
|
448
|
+
readonly enum: readonly ["top_left", "top_center", "top_right", "middle_left", "middle_center", "middle_right", "bottom_left", "bottom_center", "bottom_right"];
|
|
449
|
+
readonly description: "Caption region in Premiere's Align and Transform zone grid.";
|
|
450
|
+
};
|
|
451
|
+
readonly offset_x_px: {
|
|
452
|
+
readonly type: "integer";
|
|
453
|
+
readonly minimum: -2000;
|
|
454
|
+
readonly maximum: 2000;
|
|
455
|
+
readonly description: "Horizontal position offset from the zone in pixels.";
|
|
456
|
+
};
|
|
457
|
+
readonly offset_y_px: {
|
|
458
|
+
readonly type: "integer";
|
|
459
|
+
readonly minimum: -2000;
|
|
460
|
+
readonly maximum: 2000;
|
|
461
|
+
readonly description: "Vertical position offset from the zone in pixels; negative moves up.";
|
|
462
|
+
};
|
|
463
|
+
readonly track_style_name: {
|
|
464
|
+
readonly type: "string";
|
|
465
|
+
readonly minLength: 1;
|
|
466
|
+
readonly maxLength: 80;
|
|
467
|
+
readonly description: "Name for the saved Track Style (default Caption Track Style).";
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
};
|
|
346
471
|
};
|
|
347
472
|
};
|
|
348
|
-
handler: (args: {
|
|
349
|
-
preset?: string;
|
|
350
|
-
}) => Promise<{
|
|
473
|
+
handler: (args: Record<string, unknown>) => Promise<{
|
|
351
474
|
success: boolean;
|
|
352
475
|
data: {
|
|
353
|
-
style: import("../ai/caption-authoring.js").CaptionStyleDescriptor
|
|
476
|
+
style: import("../ai/caption-authoring.js").CaptionStyleDescriptor | {
|
|
477
|
+
custom_style: import("../ai/caption-authoring.js").CustomCaptionStyle;
|
|
478
|
+
source: string;
|
|
479
|
+
preset: CaptionStylePreset;
|
|
480
|
+
font_family_suggestion: string;
|
|
481
|
+
font_weight: string;
|
|
482
|
+
font_size_percent_of_height: number;
|
|
483
|
+
position: {
|
|
484
|
+
x: number;
|
|
485
|
+
y: number;
|
|
486
|
+
};
|
|
487
|
+
alignment: "center";
|
|
488
|
+
stroke: boolean;
|
|
489
|
+
background: boolean;
|
|
490
|
+
word_highlight: boolean;
|
|
491
|
+
uppercase_recommended: boolean;
|
|
492
|
+
safe_zone_recommendation: Record<import("../ai/caption-authoring.js").SafeZonePlatform, {
|
|
493
|
+
x: number;
|
|
494
|
+
y: number;
|
|
495
|
+
}>;
|
|
496
|
+
note: string;
|
|
497
|
+
} | {
|
|
498
|
+
custom_style: import("../ai/caption-authoring.js").CustomCaptionStyle;
|
|
499
|
+
source: string;
|
|
500
|
+
preset: null;
|
|
501
|
+
};
|
|
354
502
|
workflow: string[];
|
|
355
503
|
premiere_ui_steps: string[];
|
|
504
|
+
track_style: {
|
|
505
|
+
name: string;
|
|
506
|
+
scope: "A Track Style applies to every caption on the caption track. Style one caption, save it with Track Style > + Create style, and the whole track updates; no multi-select is needed.";
|
|
507
|
+
reusable: "A saved Track Style can be applied to the caption track of any other sequence in the project from the Track Style dropdown.";
|
|
508
|
+
project_item: "Each Track Style is stored as a project item in the Project panel; keep it in a shared bin so it can be found and reused.";
|
|
509
|
+
};
|
|
510
|
+
limitations: ("Premiere Pro exposes no documented ExtendScript, QE DOM, or UXP API that creates, edits, or applies caption styles, so these steps must be performed in the Premiere UI." | "These steps target Premiere Pro 2024 and later. Earlier versions lack the Track Style workflow described here and were not checked; panel labels can also differ between versions and UI languages." | "Verify the rendered result with export_frame or export_sequence_review_frames; this tool cannot read the applied style back.")[];
|
|
511
|
+
warnings: string[];
|
|
512
|
+
applied: boolean;
|
|
356
513
|
mutation_refused: string;
|
|
357
514
|
safe_zones: Record<"x" | "tiktok" | "instagram_reels" | "youtube_shorts" | "instagram_feed" | "youtube" | "linkedin", {
|
|
358
515
|
x: number;
|
|
359
516
|
y: number;
|
|
360
517
|
}>;
|
|
361
|
-
routes: ("read_sequence_captions" | "transform_track_item_uxp" | "export_sequence_review_frames" | "create_caption_track" | "set_clip_position" | "inspect_caption_tracks_uxp")[];
|
|
518
|
+
routes: ("read_sequence_captions" | "transform_track_item_uxp" | "export_frame" | "export_sequence_review_frames" | "create_caption_track" | "set_clip_position" | "inspect_caption_tracks_uxp")[];
|
|
362
519
|
};
|
|
363
520
|
error?: undefined;
|
|
364
521
|
} | {
|
|
@@ -1,10 +1,63 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { buildCaptionArtifact, CAPTION_AUTHORING_ROUTES, CAPTION_STYLE_PRESETS, checkCaptionSafeZone, describeCaptionStyle, MAX_EMPHASIS_WORDS, MAX_FILLER_TOKENS, MAX_FRAME_DIMENSION, MAX_SAFE_ZONE_ELEMENTS, MIN_FRAME_DIMENSION, SAFE_ZONE_ELEMENT_KINDS, SAFE_ZONE_PLATFORMS, SAFE_ZONE_ROUTES, } from "../ai/caption-authoring.js";
|
|
4
|
+
import { buildCaptionArtifact, CAPTION_AUTHORING_ROUTES, CAPTION_SHADOW_DEFAULTS, CAPTION_STROKE_PLACEMENTS, CAPTION_STYLE_PRESETS, CAPTION_TRACK_STYLE_GUIDANCE, CAPTION_ZONES, buildCustomCaptionStyleSteps, buildPresetCaptionStyleSteps, captionSafeZoneRecommendation, checkCaptionSafeZone, CUSTOM_CAPTION_STYLE_LIMITS, customCaptionStyleWarnings, DEFAULT_TRACK_STYLE_NAME, describeCaptionStyle, MAX_CAPTION_STROKES, validateCustomCaptionStyle, MAX_EMPHASIS_WORDS, MAX_FILLER_TOKENS, MAX_FRAME_DIMENSION, MAX_SAFE_ZONE_ELEMENTS, MIN_FRAME_DIMENSION, SAFE_ZONE_ELEMENT_KINDS, SAFE_ZONE_PLATFORMS, SAFE_ZONE_ROUTES, } from "../ai/caption-authoring.js";
|
|
5
5
|
import { WORD_TIMELINE_PARAMETER } from "../ai/word-timeline.js";
|
|
6
6
|
export const MAX_INLINE_ARTIFACT_BYTES = 512 * 1024;
|
|
7
7
|
const MAX_PATH_LENGTH = 4096;
|
|
8
|
+
const CAPTION_STYLE_VERIFY_ROUTES = ["export_frame", "export_sequence_review_frames"];
|
|
9
|
+
const HEX_COLOR_PATTERN = "^#[0-9A-Fa-f]{6}$";
|
|
10
|
+
const LIMITS = CUSTOM_CAPTION_STYLE_LIMITS;
|
|
11
|
+
const shadowNumber = (key, label) => ({
|
|
12
|
+
type: "number",
|
|
13
|
+
minimum: LIMITS.shadow[key].min,
|
|
14
|
+
maximum: LIMITS.shadow[key].max,
|
|
15
|
+
description: `${label} (default ${CAPTION_SHADOW_DEFAULTS[key]}).`,
|
|
16
|
+
});
|
|
17
|
+
const CUSTOM_STYLE_PARAMETER = {
|
|
18
|
+
type: "object",
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
description: "Optional caption look to translate into concrete Premiere UI steps. Every field is optional; at least one is required. Values are documentation only and are never applied.",
|
|
21
|
+
properties: {
|
|
22
|
+
font_family: { type: "string", minLength: 1, maxLength: LIMITS.font_family_max_length, description: "Font family name as shown in Premiere, e.g. Source Han Sans TW." },
|
|
23
|
+
font_weight: { type: "string", minLength: 1, maxLength: LIMITS.font_weight_max_length, description: "Font weight or style name, e.g. Heavy or Bold." },
|
|
24
|
+
font_size_px: { type: "integer", minimum: LIMITS.font_size_px.min, maximum: LIMITS.font_size_px.max, description: "Font size in Premiere's caption units (pixels)." },
|
|
25
|
+
fill_color: { type: "string", pattern: HEX_COLOR_PATTERN, description: "Text fill colour as #RRGGBB." },
|
|
26
|
+
strokes: {
|
|
27
|
+
type: "array",
|
|
28
|
+
maxItems: MAX_CAPTION_STROKES,
|
|
29
|
+
description: "Up to three strokes in Premiere's Stroke list order; each stroke after the first is added with + next to Stroke. Layered outer strokes need increasing widths.",
|
|
30
|
+
items: {
|
|
31
|
+
type: "object",
|
|
32
|
+
additionalProperties: false,
|
|
33
|
+
description: "One stroke.",
|
|
34
|
+
properties: {
|
|
35
|
+
color: { type: "string", pattern: HEX_COLOR_PATTERN, description: "Stroke colour as #RRGGBB." },
|
|
36
|
+
width_px: { type: "number", minimum: LIMITS.stroke_width_px.min, maximum: LIMITS.stroke_width_px.max, description: "Stroke width." },
|
|
37
|
+
placement: { type: "string", enum: [...CAPTION_STROKE_PLACEMENTS], description: "Stroke placement (default outer)." },
|
|
38
|
+
},
|
|
39
|
+
required: ["color", "width_px"],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
shadow: {
|
|
43
|
+
type: "object",
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
description: "Drop shadow; omitted fields use the listed defaults.",
|
|
46
|
+
properties: {
|
|
47
|
+
color: { type: "string", pattern: HEX_COLOR_PATTERN, description: `Shadow colour as #RRGGBB (default ${CAPTION_SHADOW_DEFAULTS.color}).` },
|
|
48
|
+
opacity: shadowNumber("opacity", "Shadow opacity percent"),
|
|
49
|
+
angle: shadowNumber("angle", "Shadow angle in degrees"),
|
|
50
|
+
distance: shadowNumber("distance", "Shadow distance"),
|
|
51
|
+
size: shadowNumber("size", "Shadow size"),
|
|
52
|
+
blur: shadowNumber("blur", "Shadow blur"),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
zone: { type: "string", enum: [...CAPTION_ZONES], description: "Caption region in Premiere's Align and Transform zone grid." },
|
|
56
|
+
offset_x_px: { type: "integer", minimum: LIMITS.offset_px.min, maximum: LIMITS.offset_px.max, description: "Horizontal position offset from the zone in pixels." },
|
|
57
|
+
offset_y_px: { type: "integer", minimum: LIMITS.offset_px.min, maximum: LIMITS.offset_px.max, description: "Vertical position offset from the zone in pixels; negative moves up." },
|
|
58
|
+
track_style_name: { type: "string", minLength: 1, maxLength: LIMITS.track_style_name_max_length, description: `Name for the saved Track Style (default ${DEFAULT_TRACK_STYLE_NAME}).` },
|
|
59
|
+
},
|
|
60
|
+
};
|
|
8
61
|
function absolutePath(value, label) {
|
|
9
62
|
if (typeof value !== "string" || !value.trim() || value.length > MAX_PATH_LENGTH)
|
|
10
63
|
throw new Error(`${label} must be a non-empty string of at most ${MAX_PATH_LENGTH} characters`);
|
|
@@ -195,22 +248,30 @@ export function getCaptionAuthoringTools() {
|
|
|
195
248
|
},
|
|
196
249
|
},
|
|
197
250
|
get_caption_style_guidance: {
|
|
198
|
-
description: "
|
|
251
|
+
description: "Read-only caption style guidance: a preset (clean, bold_pop, karaoke, podcast, lecture) and/or a custom_style spec (font, size, fill, up to 3 strokes, shadow, zone, offsets) turned into Premiere 2024+ Properties-panel steps that save a reusable whole-track Track Style. NEVER modifies caption tracks; Premiere has no caption style API.",
|
|
199
252
|
parameters: {
|
|
200
253
|
type: "object",
|
|
201
254
|
additionalProperties: false,
|
|
202
255
|
required: [],
|
|
203
256
|
properties: {
|
|
204
|
-
preset: { type: "string", enum: [...CAPTION_STYLE_PRESETS], description: "Style preset to describe (default clean)." },
|
|
257
|
+
preset: { type: "string", enum: [...CAPTION_STYLE_PRESETS], description: "Style preset to describe (default clean when custom_style is omitted)." },
|
|
258
|
+
custom_style: CUSTOM_STYLE_PARAMETER,
|
|
205
259
|
},
|
|
206
260
|
},
|
|
207
261
|
handler: async (args) => {
|
|
208
262
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
263
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
264
|
+
throw new Error("arguments must be an object");
|
|
265
|
+
if (args.preset !== undefined && (typeof args.preset !== "string" || !CAPTION_STYLE_PRESETS.includes(args.preset))) {
|
|
211
266
|
throw new Error(`preset must be one of: ${CAPTION_STYLE_PRESETS.join(", ")}`);
|
|
212
267
|
}
|
|
213
|
-
const
|
|
268
|
+
const custom = args.custom_style === undefined ? undefined : validateCustomCaptionStyle(args.custom_style);
|
|
269
|
+
const preset = (args.preset ?? (custom ? undefined : "clean"));
|
|
270
|
+
const presetStyle = preset ? describeCaptionStyle(preset) : undefined;
|
|
271
|
+
const style = custom
|
|
272
|
+
? { ...(presetStyle ?? { preset: null }), custom_style: custom, source: presetStyle ? "preset_with_custom_style" : "custom_style" }
|
|
273
|
+
: presetStyle;
|
|
274
|
+
const premiereUiSteps = custom ? buildCustomCaptionStyleSteps(custom, preset) : buildPresetCaptionStyleSteps(presetStyle);
|
|
214
275
|
return {
|
|
215
276
|
success: true,
|
|
216
277
|
data: {
|
|
@@ -220,29 +281,21 @@ export function getCaptionAuthoringTools() {
|
|
|
220
281
|
"Step 2: Import the caption file into Premiere project",
|
|
221
282
|
"Step 3: Call create_caption_track with action:import and the imported item_id",
|
|
222
283
|
"Step 4: Inspect the track with read_sequence_captions or inspect_caption_tracks_uxp",
|
|
223
|
-
"Step 5:
|
|
284
|
+
"Step 5: Style one caption in Premiere's Properties panel and save it as a Track Style (premiere_ui_steps); it applies to the whole caption track",
|
|
224
285
|
],
|
|
225
|
-
premiere_ui_steps:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
: []),
|
|
239
|
-
...(style.uppercase_recommended
|
|
240
|
-
? [" - Transform: uppercase recommended for this preset"]
|
|
241
|
-
: []),
|
|
242
|
-
],
|
|
243
|
-
mutation_refused: "This tool provides guidance only. MCP for Adobe Premiere Pro does not expose a CaptionTrack style mutation API because Premiere does not expose one through documented ExtendScript, QE DOM, or UXP surfaces. Apply caption styles manually in the Essential Graphics panel.",
|
|
244
|
-
safe_zones: style.safe_zone_recommendation,
|
|
245
|
-
routes: [...CAPTION_AUTHORING_ROUTES, ...SAFE_ZONE_ROUTES],
|
|
286
|
+
premiere_ui_steps: premiereUiSteps,
|
|
287
|
+
track_style: {
|
|
288
|
+
name: custom?.track_style_name ?? (custom ? DEFAULT_TRACK_STYLE_NAME : `${preset} captions`),
|
|
289
|
+
scope: CAPTION_TRACK_STYLE_GUIDANCE.scope,
|
|
290
|
+
reusable: CAPTION_TRACK_STYLE_GUIDANCE.reusable,
|
|
291
|
+
project_item: CAPTION_TRACK_STYLE_GUIDANCE.project_item,
|
|
292
|
+
},
|
|
293
|
+
limitations: [...CAPTION_TRACK_STYLE_GUIDANCE.limitations],
|
|
294
|
+
warnings: custom ? customCaptionStyleWarnings(custom) : [],
|
|
295
|
+
applied: false,
|
|
296
|
+
mutation_refused: "This tool provides guidance only. MCP for Adobe Premiere Pro does not expose a CaptionTrack style mutation API because Premiere does not expose one through documented ExtendScript, QE DOM, or UXP surfaces. Apply caption styles manually in Premiere's Properties panel (or Essential Graphics) and save them as a Track Style.",
|
|
297
|
+
safe_zones: presetStyle?.safe_zone_recommendation ?? captionSafeZoneRecommendation(),
|
|
298
|
+
routes: [...CAPTION_AUTHORING_ROUTES, ...SAFE_ZONE_ROUTES, ...CAPTION_STYLE_VERIFY_ROUTES],
|
|
246
299
|
},
|
|
247
300
|
};
|
|
248
301
|
}
|
package/dist/tools/health.d.ts
CHANGED
package/dist/tools/timeline.js
CHANGED
|
@@ -1654,13 +1654,15 @@ export function getTimelineTools(bridgeOptions, dependencies = {}) {
|
|
|
1654
1654
|
if (Math.abs(parseFloat(cand.start.ticks) - oldStart) > __TICK_MATCH_TOL) continue;
|
|
1655
1655
|
replacement = cand;
|
|
1656
1656
|
}
|
|
1657
|
-
// The item's Out mark can land up to one media frame
|
|
1658
|
-
//
|
|
1659
|
-
//
|
|
1657
|
+
// The item's Out mark can land up to one media frame off the 25 fps
|
|
1658
|
+
// span (early without write bias, or one media frame long when the
|
|
1659
|
+
// closer media frame is past the tick boundary). Pull the placed end
|
|
1660
|
+
// back onto the original span when the drift is within one media
|
|
1661
|
+
// frame plus one sequence frame.
|
|
1660
1662
|
var endCorrected = false;
|
|
1661
1663
|
if (replacement) {
|
|
1662
1664
|
var endGap = oldEnd - parseFloat(replacement.end.ticks);
|
|
1663
|
-
if (endGap > __TICK_MATCH_TOL && endGap <= markTolerance + __sequenceFrameTicks(seq)) {
|
|
1665
|
+
if (Math.abs(endGap) > __TICK_MATCH_TOL && Math.abs(endGap) <= markTolerance + __sequenceFrameTicks(seq)) {
|
|
1664
1666
|
var endTime = new Time();
|
|
1665
1667
|
endTime.ticks = String(oldEnd);
|
|
1666
1668
|
try { replacement.end = endTime; endCorrected = true; } catch (eEnd) {}
|
|
@@ -165,7 +165,7 @@ operation” when the tool has no enum-based mode.
|
|
|
165
165
|
| `get_bin_contents` | Default profile | Single operation | Get detailed contents of a specific bin (folder) including all nested items, media paths, offline status, color labels, and metadata. Searches by bin name or node ID. |
|
|
166
166
|
| `get_bridge_telemetry` | Default profile | Single operation | Inspect privacy-preserving aggregate bridge health: pending command/response counts, busy operations, queue age, and CEP heartbeat state without returning project or personal data. |
|
|
167
167
|
| `get_capabilities` | Default profile | Single operation | Discover Premiere operations and report backend coverage, authority, and verification requirements. Use tool_query with task keywords (for example transcript, captions, or review frames) for ranked, bounded matches available in this session. Use tool_names for exact lookups or tool_offset/tool_limit for paging. Discovery never grants authority or proves a live host is ready. |
|
|
168
|
-
| `get_caption_style_guidance` | Default profile | `preset`: `clean`, `bold_pop`, `karaoke`, `podcast`, `lecture` |
|
|
168
|
+
| `get_caption_style_guidance` | Default profile | `preset`: `clean`, `bold_pop`, `karaoke`, `podcast`, `lecture` | Read-only caption style guidance: a preset (clean, bold_pop, karaoke, podcast, lecture) and/or a custom_style spec (font, size, fill, up to 3 strokes, shadow, zone, offsets) turned into Premiere 2024+ Properties-panel steps that save a reusable whole-track Track Style. NEVER modifies caption tracks; Premiere has no caption style API. |
|
|
169
169
|
| `get_clip_adjustment_layer` | Default profile | Single operation | Check if a clip is an adjustment layer |
|
|
170
170
|
| `get_clip_at_playhead` | Default profile | `track_type`: `video`, `audio`, `both` | Get all clips at the current playhead position across all tracks. |
|
|
171
171
|
| `get_clip_at_position` | Default profile | `track_type`: `video`, `audio` | Get the clip at a specific time position on a track |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "premiere-pro-mcp",
|
|
3
3
|
"mcpName": "io.github.leancoderkavy/premiere-pro",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.24.0",
|
|
5
5
|
"description": "MCP server for Adobe Premiere Pro with 434 core AI video editing tools, a guarded After Effects MOGRT studio, and a capability-aware UXP bridge for supported Premiere workflows.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "MCP for Adobe Premiere Pro",
|
|
11
11
|
"mcpName": "io.github.leancoderkavy/premiere-pro",
|
|
12
12
|
"npmPackage": "premiere-pro-mcp",
|
|
13
|
-
"version": "1.
|
|
13
|
+
"version": "1.24.0",
|
|
14
14
|
"repository": "https://github.com/leancoderkavy/premiere-pro-mcp",
|
|
15
15
|
"homepage": "https://premiere-pro-mcp.com/",
|
|
16
16
|
"license": "MIT",
|
package/uxp-plugin/manifest.json
CHANGED