@kenkaiiii/gg-editor-premiere-panel 0.2.1

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.
@@ -0,0 +1,161 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Local HTTP server inside the CEP panel.
4
+ *
5
+ * Wire protocol — same as the macOS osascript bridge:
6
+ * POST /rpc
7
+ * { "method": "get_timeline", "params": {} }
8
+ * →
9
+ * { "ok": true, "result": {...} }
10
+ * { "ok": false, "error": "..." }
11
+ *
12
+ * Listens on 127.0.0.1 only — never exposed beyond localhost. The port is
13
+ * fixed (default 7437) so the gg-editor adapter can find it without
14
+ * discovery; override via env GG_EDITOR_PREMIERE_PORT before launching
15
+ * Premiere.
16
+ */
17
+ (function () {
18
+ var http = cep_node ? cep_node.require("http") : null;
19
+ if (!http) {
20
+ setStatus("err", "no nodejs runtime — set --enable-nodejs in manifest");
21
+ return;
22
+ }
23
+
24
+ var DEFAULT_PORT = 7437;
25
+ var port = parseInt(
26
+ (cep_node.process && cep_node.process.env && cep_node.process.env.GG_EDITOR_PREMIERE_PORT) ||
27
+ DEFAULT_PORT,
28
+ 10,
29
+ );
30
+
31
+ var cs = new CSInterface();
32
+ var reqCount = 0;
33
+
34
+ function setStatus(kind, label) {
35
+ var el = document.getElementById("status");
36
+ if (!el) return;
37
+ el.textContent = label;
38
+ el.className = "pill " + (kind || "");
39
+ }
40
+ function setPort(p) {
41
+ var el = document.getElementById("port");
42
+ if (el) el.textContent = String(p);
43
+ }
44
+ function bumpReqs() {
45
+ reqCount += 1;
46
+ var el = document.getElementById("reqs");
47
+ if (el) el.textContent = String(reqCount);
48
+ }
49
+ function setLastErr(msg) {
50
+ var el = document.getElementById("lastErr");
51
+ if (!el) return;
52
+ el.textContent = msg || "—";
53
+ el.className = msg ? "err" : "muted";
54
+ }
55
+
56
+ /**
57
+ * JSX is loaded automatically by CEP via manifest <ScriptPath>. We invoke
58
+ * named functions defined there: gg_ping, gg_get_timeline, etc.
59
+ */
60
+ function callJsx(method, params) {
61
+ return new Promise(function (resolve) {
62
+ var fn = "gg_" + method;
63
+ // Stringify params safely for embedding in a JSX call.
64
+ var jsonParams = JSON.stringify(params || {})
65
+ .replace(/\\/g, "\\\\")
66
+ .replace(/"/g, '\\"');
67
+ var script = fn + '("' + jsonParams + '")';
68
+ cs.evalScript(script, function (raw) {
69
+ if (typeof raw !== "string") {
70
+ resolve({ ok: false, error: "evalScript returned non-string" });
71
+ return;
72
+ }
73
+ if (raw === "EvalScript error.") {
74
+ resolve({ ok: false, error: "JSX evalScript error (method=" + method + ")" });
75
+ return;
76
+ }
77
+ try {
78
+ var parsed = JSON.parse(raw);
79
+ resolve(parsed);
80
+ } catch (e) {
81
+ resolve({ ok: false, error: "JSX returned non-JSON: " + String(raw).slice(0, 200) });
82
+ }
83
+ });
84
+ });
85
+ }
86
+
87
+ function readBody(req) {
88
+ return new Promise(function (resolve, reject) {
89
+ var chunks = [];
90
+ req.on("data", function (c) { chunks.push(c); });
91
+ req.on("end", function () {
92
+ try { resolve(Buffer.concat(chunks).toString("utf8")); }
93
+ catch (e) { reject(e); }
94
+ });
95
+ req.on("error", reject);
96
+ });
97
+ }
98
+
99
+ var server = http.createServer(function (req, res) {
100
+ res.setHeader("Access-Control-Allow-Origin", "*");
101
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
102
+
103
+ if (req.method === "GET" && req.url === "/health") {
104
+ res.writeHead(200);
105
+ // `kind` lets the gg-editor bridge tell CEP panels apart from a future
106
+ // UXP panel (both speak the same /rpc protocol). Useful for surfacing
107
+ // the Sept 2026 ExtendScript sunset to users still on CEP.
108
+ res.end(
109
+ JSON.stringify({
110
+ ok: true,
111
+ product: "gg-editor-premiere-panel",
112
+ port: port,
113
+ kind: "cep",
114
+ }),
115
+ );
116
+ return;
117
+ }
118
+
119
+ if (req.method !== "POST" || req.url !== "/rpc") {
120
+ res.writeHead(404);
121
+ res.end(JSON.stringify({ ok: false, error: "POST /rpc only" }));
122
+ return;
123
+ }
124
+
125
+ bumpReqs();
126
+ readBody(req)
127
+ .then(function (body) {
128
+ var msg;
129
+ try { msg = JSON.parse(body); }
130
+ catch (e) {
131
+ res.writeHead(400);
132
+ res.end(JSON.stringify({ ok: false, error: "bad json" }));
133
+ return null;
134
+ }
135
+ if (!msg || typeof msg.method !== "string") {
136
+ res.writeHead(400);
137
+ res.end(JSON.stringify({ ok: false, error: "missing method" }));
138
+ return null;
139
+ }
140
+ return callJsx(msg.method, msg.params || {}).then(function (result) {
141
+ res.writeHead(200);
142
+ res.end(JSON.stringify(result));
143
+ });
144
+ })
145
+ .catch(function (err) {
146
+ setLastErr(String(err && err.message ? err.message : err));
147
+ res.writeHead(500);
148
+ res.end(JSON.stringify({ ok: false, error: String(err) }));
149
+ });
150
+ });
151
+
152
+ server.listen(port, "127.0.0.1", function () {
153
+ setStatus("ok", "listening");
154
+ setPort(port);
155
+ });
156
+
157
+ server.on("error", function (err) {
158
+ setStatus("err", "bind failed");
159
+ setLastErr(err.code === "EADDRINUSE" ? "port " + port + " already in use" : String(err));
160
+ });
161
+ })();
@@ -0,0 +1,43 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Shared constants for the Premiere UXP commands.
4
+ *
5
+ * Premiere stores positions in **ticks** at a fixed quantum:
6
+ * 254_016_000_000 ticks = 1 second
7
+ * (verified in adb-mcp + AdobeDocs/uxp-premiere-pro-samples)
8
+ *
9
+ * To convert frames ↔ ticks you also need the sequence's per-frame timebase
10
+ * (sequence.getTimebase() returns the tick count per frame). fps is then
11
+ * TICKS_PER_SECOND / timebase.
12
+ */
13
+
14
+ const TICKS_PER_SECOND = 254016000000;
15
+
16
+ /**
17
+ * Track type discriminator returned by `track.getType()`. Mirror of the
18
+ * enum in `premierepro` we care about for filtering tracks.
19
+ */
20
+ const TRACK_TYPE = {
21
+ VIDEO: "video",
22
+ AUDIO: "audio",
23
+ CAPTION: "caption",
24
+ };
25
+
26
+ /**
27
+ * Premiere's marker color palette is an 8-entry index. These are the names
28
+ * the gg-editor adapter uses (see `../core/marker-colors.ts` on that side);
29
+ * we map them through Premiere's `Marker.MARKER_COLOR_*` enum if available
30
+ * at runtime, falling back to the documented numeric ordering.
31
+ */
32
+ const MARKER_COLOR_INDEX = {
33
+ green: 0,
34
+ red: 1,
35
+ purple: 2,
36
+ orange: 3,
37
+ yellow: 4,
38
+ white: 5,
39
+ blue: 6,
40
+ cyan: 7,
41
+ };
42
+
43
+ module.exports = { TICKS_PER_SECOND, TRACK_TYPE, MARKER_COLOR_INDEX };
@@ -0,0 +1,401 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * RPC method handlers for the Premiere UXP plugin.
4
+ *
5
+ * Each method is invoked by `main.js` after parsing a wire message of the
6
+ * form `{ id, method, params }`. Methods return raw values; main.js
7
+ * wraps `{ ok: true, result }` and serialises errors via try/catch.
8
+ *
9
+ * Wire shapes intentionally mirror the CEP panel's JSX runtime so the
10
+ * gg-editor adapter doesn't need to branch on transport.
11
+ */
12
+
13
+ const ppro = require("premierepro");
14
+ const { TICKS_PER_SECOND, TRACK_TYPE, MARKER_COLOR_INDEX } = require("./consts.js");
15
+ const {
16
+ getActiveProject,
17
+ getActiveSequence,
18
+ findProjectItemByName,
19
+ fpsOf,
20
+ framesToTicks,
21
+ ticksToFrames,
22
+ withTransaction,
23
+ } = require("./utils.js");
24
+
25
+ // ── Helpers specific to this layer ──────────────────────────
26
+
27
+ function colorIndex(name) {
28
+ const k = (name || "blue").toLowerCase();
29
+ return Object.prototype.hasOwnProperty.call(MARKER_COLOR_INDEX, k)
30
+ ? MARKER_COLOR_INDEX[k]
31
+ : MARKER_COLOR_INDEX.blue;
32
+ }
33
+
34
+ function basename(p) {
35
+ return String(p).replace(/\\/g, "/").split("/").pop();
36
+ }
37
+
38
+ async function collectClips(sequence, kind) {
39
+ const out = [];
40
+ let count;
41
+ if (kind === TRACK_TYPE.VIDEO) count = await sequence.getVideoTrackCount();
42
+ else if (kind === TRACK_TYPE.AUDIO) count = await sequence.getAudioTrackCount();
43
+ else return out;
44
+
45
+ for (let ti = 0; ti < count; ti++) {
46
+ const track =
47
+ kind === TRACK_TYPE.VIDEO
48
+ ? await sequence.getVideoTrack(ti)
49
+ : await sequence.getAudioTrack(ti);
50
+ if (!track) continue;
51
+ // 1 = clips (TrackItem.kClipType_Clip); skip transitions.
52
+ const items = await track.getTrackItems(1, false);
53
+ for (let ci = 0; ci < items.length; ci++) {
54
+ const c = items[ci];
55
+ try {
56
+ const startTicks = (await c.getStartTime()).ticks;
57
+ const endTicks = (await c.getEndTime()).ticks;
58
+ const projectItem = await c.getProjectItem();
59
+ const name = projectItem ? projectItem.name : "(unnamed)";
60
+ out.push({
61
+ id: name + ":" + ti + ":" + ci,
62
+ track: ti + 1,
63
+ trackKind: kind,
64
+ startFrame: await ticksToFrames(parseInt(startTicks, 10), sequence),
65
+ endFrame: await ticksToFrames(parseInt(endTicks, 10), sequence),
66
+ name,
67
+ });
68
+ } catch (_) {
69
+ // Skip uninspectable items.
70
+ }
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+
76
+ // ── Method implementations ──────────────────────────────────
77
+
78
+ async function ping() {
79
+ // app.version may not be exposed via require("premierepro") on every release.
80
+ let version = "?";
81
+ try {
82
+ if (ppro.App && ppro.App.getVersion) version = await ppro.App.getVersion();
83
+ } catch (_) {}
84
+ return { product: "Premiere Pro", version };
85
+ }
86
+
87
+ async function get_timeline() {
88
+ const { sequence } = await getActiveSequence();
89
+ const fps = await fpsOf(sequence);
90
+
91
+ const videoClips = await collectClips(sequence, TRACK_TYPE.VIDEO);
92
+ const audioClips = await collectClips(sequence, TRACK_TYPE.AUDIO);
93
+ const clips = videoClips.concat(audioClips);
94
+
95
+ const markers = await collectMarkers(sequence);
96
+
97
+ const endTime = await sequence.getEndTime();
98
+ const durationFrames = endTime
99
+ ? await ticksToFrames(parseInt(endTime.ticks, 10), sequence)
100
+ : 0;
101
+
102
+ return {
103
+ name: sequence.name,
104
+ frameRate: fps,
105
+ durationFrames,
106
+ clips,
107
+ markers,
108
+ };
109
+ }
110
+
111
+ async function collectMarkers(sequence) {
112
+ const out = [];
113
+ let markers;
114
+ try {
115
+ markers = await sequence.getMarkers();
116
+ } catch (_) {
117
+ return out;
118
+ }
119
+ if (!markers) return out;
120
+ let list;
121
+ try {
122
+ list = await markers.getMarkers();
123
+ } catch (_) {
124
+ list = markers; // some API versions return the list directly
125
+ }
126
+ if (!list || !list.length) return out;
127
+
128
+ for (const m of list) {
129
+ try {
130
+ const startTicks = parseInt((await m.getStart()).ticks, 10);
131
+ let endTicks = startTicks;
132
+ try {
133
+ endTicks = parseInt((await m.getEnd()).ticks, 10);
134
+ } catch (_) {}
135
+ const startFrame = await ticksToFrames(startTicks, sequence);
136
+ const endFrame = await ticksToFrames(endTicks, sequence);
137
+ let comments = "";
138
+ let name = "";
139
+ try {
140
+ comments = await m.getComments();
141
+ } catch (_) {}
142
+ try {
143
+ name = await m.getName();
144
+ } catch (_) {}
145
+ let color = 6;
146
+ try {
147
+ color = await m.getColor();
148
+ } catch (_) {}
149
+ out.push({
150
+ frame: startFrame,
151
+ note: comments || name || "",
152
+ color,
153
+ durationFrames: Math.max(0, endFrame - startFrame),
154
+ });
155
+ } catch (_) {
156
+ // Skip unreadable marker.
157
+ }
158
+ }
159
+ return out;
160
+ }
161
+
162
+ async function get_markers() {
163
+ const { sequence } = await getActiveSequence();
164
+ return collectMarkers(sequence);
165
+ }
166
+
167
+ async function add_marker(params) {
168
+ const { sequence, project } = await getActiveSequence();
169
+ const startTicks = await framesToTicks(params.frame || 0, sequence);
170
+ const durationFrames = Math.max(1, params.durationFrames || 1);
171
+ const endTicks = await framesToTicks((params.frame || 0) + durationFrames, sequence);
172
+ const note = params.note || "";
173
+ const color = colorIndex(params.color);
174
+
175
+ const markers = await sequence.getMarkers();
176
+ if (!markers) throw new Error("Sequence has no markers collection.");
177
+
178
+ withTransaction(project, () => {
179
+ const startTickTime = ppro.TickTime.createWithTicks(String(startTicks));
180
+ const endTickTime = ppro.TickTime.createWithTicks(String(endTicks));
181
+ const action = markers.createAddMarkerAction(
182
+ note.substring(0, 60) || "marker",
183
+ "Comment",
184
+ startTickTime,
185
+ endTickTime,
186
+ note,
187
+ );
188
+ return [action];
189
+ });
190
+
191
+ // Color is set in a follow-up transaction; older API versions only expose
192
+ // setColor outside the create call. Best-effort: swallow errors.
193
+ try {
194
+ const list = await (await sequence.getMarkers()).getMarkers();
195
+ if (list && list.length) {
196
+ const last = list[list.length - 1];
197
+ if (last && last.createSetColorAction) {
198
+ withTransaction(project, () => [last.createSetColorAction(color)]);
199
+ }
200
+ }
201
+ } catch (_) {}
202
+
203
+ return null;
204
+ }
205
+
206
+ async function append_clip(params) {
207
+ const { project, sequence } = await getActiveSequence();
208
+ const trackIndex = Math.max(1, params.track || 1) - 1;
209
+
210
+ await ppro.Project.importFiles(project, [params.mediaPath], { addToActiveSequence: false });
211
+ const item = await findProjectItemByName(project, basename(params.mediaPath));
212
+ if (!item) throw new Error("Imported item not found in project: " + params.mediaPath);
213
+
214
+ const track = await sequence.getVideoTrack(trackIndex);
215
+ if (!track) throw new Error("Video track " + (trackIndex + 1) + " does not exist.");
216
+
217
+ const endTime = await sequence.getEndTime();
218
+ const insertTicks = endTime ? parseInt(endTime.ticks, 10) : 0;
219
+
220
+ withTransaction(project, () => {
221
+ const tickTime = ppro.TickTime.createWithTicks(String(insertTicks));
222
+ const action = track.createInsertProjectItemAction(item, tickTime);
223
+ return [action];
224
+ });
225
+
226
+ // Read back the inserted clip's range for the result envelope.
227
+ const clips = await track.getTrackItems(1, false);
228
+ const inserted = clips[clips.length - 1];
229
+ const startTicks = parseInt((await inserted.getStartTime()).ticks, 10);
230
+ const endTicks = parseInt((await inserted.getEndTime()).ticks, 10);
231
+ const projectItem = await inserted.getProjectItem();
232
+
233
+ return {
234
+ id: (projectItem ? projectItem.name : "clip") + ":" + trackIndex + ":" + (clips.length - 1),
235
+ track: trackIndex + 1,
236
+ trackKind: "video",
237
+ startFrame: await ticksToFrames(startTicks, sequence),
238
+ endFrame: await ticksToFrames(endTicks, sequence),
239
+ name: projectItem ? projectItem.name : params.mediaPath,
240
+ };
241
+ }
242
+
243
+ async function replace_clip(params) {
244
+ const { project, sequence } = await getActiveSequence();
245
+ if (!params.clipId) throw new Error("replace_clip: clipId required");
246
+ if (!params.mediaPath) throw new Error("replace_clip: mediaPath required");
247
+
248
+ // clipId encodes track:index by our convention from get_timeline().
249
+ const parts = String(params.clipId).split(":");
250
+ const trackIndex = parseInt(parts[parts.length - 2], 10);
251
+ const clipIndex = parseInt(parts[parts.length - 1], 10);
252
+ if (!Number.isFinite(trackIndex) || !Number.isFinite(clipIndex)) {
253
+ throw new Error("replace_clip: malformed clipId (expected name:track:index)");
254
+ }
255
+
256
+ await ppro.Project.importFiles(project, [params.mediaPath], { addToActiveSequence: false });
257
+ const newItem = await findProjectItemByName(project, basename(params.mediaPath));
258
+ if (!newItem) throw new Error("Imported item not found in project: " + params.mediaPath);
259
+
260
+ const track = await sequence.getVideoTrack(trackIndex);
261
+ if (!track) throw new Error("Video track " + (trackIndex + 1) + " does not exist.");
262
+
263
+ const items = await track.getTrackItems(1, false);
264
+ const target = items[clipIndex];
265
+ if (!target) throw new Error("Clip index " + clipIndex + " does not exist on track.");
266
+
267
+ withTransaction(project, () => {
268
+ if (typeof target.createReplaceProjectItemAction === "function") {
269
+ return [target.createReplaceProjectItemAction(newItem)];
270
+ }
271
+ throw new Error("replace_clip: this Premiere version does not expose Replace via UXP.");
272
+ });
273
+
274
+ return null;
275
+ }
276
+
277
+ async function clone_timeline(params) {
278
+ const project = await getActiveProject();
279
+ const seq = await project.getActiveSequence();
280
+ if (!seq) throw new Error("No active sequence to clone.");
281
+
282
+ const newName = params.newName || seq.name + " (copy)";
283
+ if (typeof project.createCloneSequenceAction !== "function") {
284
+ throw new Error("clone_timeline: this Premiere version doesn't expose sequence cloning.");
285
+ }
286
+ withTransaction(project, () => [project.createCloneSequenceAction(seq, newName)]);
287
+
288
+ return { name: newName };
289
+ }
290
+
291
+ async function save_project() {
292
+ const project = await getActiveProject();
293
+ if (typeof project.save === "function") {
294
+ await project.save();
295
+ return null;
296
+ }
297
+ throw new Error("save_project: project.save() not exposed on this Premiere version.");
298
+ }
299
+
300
+ async function import_to_media_pool(params) {
301
+ const project = await getActiveProject();
302
+ const paths = params.paths || [];
303
+ if (!paths.length) return null;
304
+
305
+ let targetBin = null;
306
+ if (params.bin) {
307
+ targetBin = await findProjectItemByName(project, params.bin);
308
+ }
309
+ await ppro.Project.importFiles(project, paths, {
310
+ addToActiveSequence: false,
311
+ targetBin: targetBin || undefined,
312
+ });
313
+ return null;
314
+ }
315
+
316
+ async function import_subtitles(params) {
317
+ const project = await getActiveProject();
318
+ if (!params.srtPath) throw new Error("import_subtitles: srtPath required");
319
+ await ppro.Project.importFiles(project, [params.srtPath], { addToActiveSequence: false });
320
+ // Premiere imports SRT as a caption project item but doesn't auto-attach to
321
+ // the sequence \u2014 the user does that manually. Surface that explicitly.
322
+ return {
323
+ imported: true,
324
+ attached: false,
325
+ note: "SRT imported into project; drag onto a caption track to attach.",
326
+ };
327
+ }
328
+
329
+ async function import_timeline(params) {
330
+ const project = await getActiveProject();
331
+ if (!params.filePath) throw new Error("import_timeline: filePath required");
332
+ await ppro.Project.importFiles(project, [params.filePath], { addToActiveSequence: true });
333
+ return null;
334
+ }
335
+
336
+ async function insert_clip_on_track(params) {
337
+ const { project, sequence } = await getActiveSequence();
338
+ const trackIndex = Math.max(1, params.track || 1) - 1;
339
+
340
+ await ppro.Project.importFiles(project, [params.mediaPath], { addToActiveSequence: false });
341
+ const item = await findProjectItemByName(project, basename(params.mediaPath));
342
+ if (!item) throw new Error("Imported item not found: " + params.mediaPath);
343
+
344
+ const track = await sequence.getVideoTrack(trackIndex);
345
+ if (!track) throw new Error("Video track " + (trackIndex + 1) + " does not exist.");
346
+
347
+ const recordTicks = await framesToTicks(params.recordFrame || 0, sequence);
348
+
349
+ withTransaction(project, () => {
350
+ const tickTime = ppro.TickTime.createWithTicks(String(recordTicks));
351
+ return [track.createInsertProjectItemAction(item, tickTime)];
352
+ });
353
+
354
+ const clips = await track.getTrackItems(1, false);
355
+ // Find the clip that starts at recordTicks (close enough \u2014 it's the one we just inserted).
356
+ let inserted = clips[clips.length - 1];
357
+ for (const c of clips) {
358
+ const t = parseInt((await c.getStartTime()).ticks, 10);
359
+ if (Math.abs(t - recordTicks) <= 1) {
360
+ inserted = c;
361
+ break;
362
+ }
363
+ }
364
+ const startTicks = parseInt((await inserted.getStartTime()).ticks, 10);
365
+ const endTicks = parseInt((await inserted.getEndTime()).ticks, 10);
366
+ const projectItem = await inserted.getProjectItem();
367
+
368
+ return {
369
+ id: (projectItem ? projectItem.name : "clip") + ":" + trackIndex + ":" + (clips.length - 1),
370
+ track: trackIndex + 1,
371
+ trackKind: "video",
372
+ startFrame: await ticksToFrames(startTicks, sequence),
373
+ endFrame: await ticksToFrames(endTicks, sequence),
374
+ name: projectItem ? projectItem.name : params.mediaPath,
375
+ };
376
+ }
377
+
378
+ // ── Dispatcher ──────────────────────────────────────────────
379
+
380
+ const HANDLERS = {
381
+ ping,
382
+ get_timeline,
383
+ get_markers,
384
+ add_marker,
385
+ append_clip,
386
+ replace_clip,
387
+ clone_timeline,
388
+ save_project,
389
+ import_to_media_pool,
390
+ import_subtitles,
391
+ import_timeline,
392
+ insert_clip_on_track,
393
+ };
394
+
395
+ async function handle(method, params) {
396
+ const fn = HANDLERS[method];
397
+ if (!fn) throw new Error("Unknown method: " + method);
398
+ return fn(params || {});
399
+ }
400
+
401
+ module.exports = { handle, HANDLERS };