See also BUILD_FINDINGS.md — consolidated LIVE-TESTED results (2026-07-17) for this machine's build, which supersede this file's research-derived claims wherever they conflict.
Extracted 2026-07 from three open-source Premiere Pro MCP servers, as
reference material for growing our own CEP bridge (host/index.jsx):
- [hetpatel]
hetpatel-11/Adobe_Premiere_Pro_MCP— 104 tools, ~43 live-tested; file-based command bridge + CEP evalScript - [leancoderkavy]
leancoderkavy/premiere-pro-mcp— 269 tools / 28 modules; file-based bridge + CEP evalScript - [ayushozha]
ayushozha/AdobePremiereProMCP— 1,020 ExtendScript functions; WebSocket server inside CEP panel
A fourth repo was studied 2026-07-17 — see the addendum at the bottom:
- [antipaster]
antipaster/Adobe-Premiere-Pro-MCP— ~170 tools; WebSocket (port 8097) → CEP panel, per-domain JSX modules; Windows, tested on Premiere 2026 — the closest peer to our build
Everything below is what those repos actually call, tagged by source. Where repos disagree, or contradict our own live tests (see QE_DOM_NOTES.md), that's flagged — treat every signature as version-dependent until verified against our Premiere build.
Two API surfaces exist:
- Standard DOM (
app.project...) — documented-ish, stabler - QE DOM (
app.enableQE(); qe.project...) — undocumented, needed for effects/transitions/razor/speed/track-removal; varies by build
TICKS_PER_SECOND = 254016000000— the single conversion constant (all three repos; matches ourindex.jsx).seq.timebase= ticks per frame, sofps = 254016000000 / Number(seq.timebase).- Ticks travel as STRINGS:
String(Math.round(seconds * 254016000000)). Timeobject construction:var t = new Time(); t.seconds = 12.5; // numeric setter // or: t.ticks = "3175200000000"; // ticks setter takes a STRING // hetpatel also saw: new Time("12.5s") — string-with-s-suffix ctor
clip.start/.end/.inPoint/.outPoint/.duration,marker.start/.end,seq.getPlayerPosition()return Time objects (.secondsnumber,.ticksstring).- QE items use
.secsnot.seconds(item.start.secs) [ayushozha]. - Which form a method wants is inconsistent per method — captured inline
below. Markers want plain seconds;
setPlayerPositionwants a ticks string; QErazorwants a ticks string [leancoderkavy, ayushozha] or an HH:MM:SS:FF timecode string [hetpatel] (version difference? verify on ours).
| Operation | Call | Source / caveat |
|---|---|---|
| Create | app.project.createNewSequence(name) |
may throw yet still create [hetpatel]; on OUR build it pops the New Sequence dialog — we use QE qe.project.newSequence(name, presetPath) instead (confirmed) |
| Create from preset | app.project.createNewSequenceFromPreset(name, sqpresetPath) |
[leancoderkavy]; did not exist on our build (we tested importSequencePreset — gone) |
| Create from clips | app.project.createNewSequenceFromClips(name, itemsArray[, targetBin]) |
auto-detects settings from first clip [leancoderkavy, ayushozha] |
| Activate | app.project.activeSequence = seq or app.project.openSequence(seq.sequenceID) or seq.openInTimeline() |
all three |
| Find | linear scan app.project.sequences[i] matching .sequenceID/.name |
"Premiere 2026 dropped getSequenceByID" [hetpatel] |
| Duplicate | seq.clone() |
renaming gotcha: seq.name = x does NOT propagate to the project panel — find the ProjectItem via item.getSequence().sequenceID === id and set item.name too [hetpatel] |
| Delete | app.project.deleteSequence(seq) |
|
| Close tab | seq.close() |
[leancoderkavy] |
| Nest | seq.createSubsequence(ignoreTrackTargetingBool) |
[hetpatel, leancoderkavy] |
| Settings read | seq.getSettings() → videoFrameWidth/Height, videoFrameRate (Time, .ticks = ticks-per-frame), audioSampleRate, videoFieldType, videoDisplayFormat, ... |
|
| Settings write | mutate then seq.setSettings(settings) |
[leancoderkavy, ayushozha] claim it works; [hetpatel] says "cannot be changed after creation"; our live tests: buggy (create-sequence non-default fps/res doesn't reliably apply). Version-dependent — don't trust it. |
| In/out | seq.setInPoint(x) / setOutPoint(x) |
arg type disagreement: ticks-string [hetpatel, leancoderkavy] vs seconds-number [ayushozha] — probe both |
| Work area | seq.setWorkAreaInPoint(ticksStr) / setWorkAreaOutPoint(ticksStr); read seq.workInPoint.ticks; seq.isWorkAreaBarEnabled() |
|
| Playhead | seq.getPlayerPosition() → Time; seq.setPlayerPosition(ticksString) |
all three |
| Zero point | seq.setZeroPoint(ticksStr); read seq.zeroPoint |
[leancoderkavy] |
| Auto-reframe | seq.autoReframeSequence(num, den, motionPreset /*'slower'|'default'|'faster'*/, newName, nestBool) |
2020+ only |
| Scene detect | seq.performSceneEditDetectionOnSelection("ApplyCuts"|"CreateMarkers", applyToLinkedAudioBool, "Low"|"Medium"|"High") |
[hetpatel] |
| Export direct (blocking) | seq.exportAsMediaDirect(outPath, eprPresetPath, workAreaType) — 0=entire, 1=in-to-out, 2=work area |
|
| Export via AME | app.encoder.launchEncoder(); app.encoder.encodeSequence(seq, outPath, presetPath, rangeConst, removeOnCompletion01); app.encoder.startBatch() |
preset must be an absolute .epr file path — a name like "H.264" silently fails (no jobID) [hetpatel]. ENCODE_ENTIRE/ENCODE_IN_TO_OUT/ENCODE_WORKAREA, ENCODE_MATCH_SEQUENCE. No progress API — fire and forget, verify output file growth. |
| Preset discovery | no API — walk .epr files on disk under AME/PPro app dirs; H.264 folder = 4B434D58_48323634 |
[leancoderkavy]; but [ayushozha] uses app.encoder.getExporters() → .getPresets() — probe it |
| Frame export | QE qeSeq.exportFramePNG(...) / exportFrameJPEG(...) |
fundamentally unreliable: arg order version-dependent (some builds (path, wStr, hStr), others (time, path)), returns false/writes nothing on some builds. [leancoderkavy] brute-forces all arg combos and verifies via the filesystem, with a one-frame AME export fallback. |
| FCP XML / AAF / OMF / EDL | seq.exportAsFinalCutProXML(path); seq.exportAsAAF(path, mixdown01, mono01, rate, bits) (also a 10-arg app.project.exportAAF); app.project.exportOMF(...); import: prefer app.project.importFiles([xmlPath], false, rootItem, false) over legacy app.openFCPXML [hetpatel]; app.importEDL(path) may pop a dialog |
|
| Import seqs from other project | app.project.importSequences(prprojPath, [seqIds]) |
|
| Captions | seq.createCaptionTrack(srtProjectItem, startSeconds, Sequence.CAPTION_FORMAT_SUBTITLE) — 3rd arg is an integer constant, NOT a string ("Illegal Parameter type" otherwise) [hetpatel]. Constants: CAPTION_FORMAT_SUBTITLE/_608/_708/_TELETEXT/_OPEN_EBU/_OP42/_OP47. [ayushozha] also: ct.addCaption(startTicks, endTicks) then set .text. No caption READ API exists — parse the source .srt instead. No caption-track delete either. |
- Collections:
seq.videoTracks/.audioTracks→.numTracks,[i]; track:.name(writable),.clips(.numItems,[i]),.transitions,.id,.index. - Add (standard DOM):
seq.insertVideoTrackAt(index, count)/insertAudioTrackAt(index, count)[leancoderkavy];seq.addTrack("video")/seq.addTrack("audio", channelType /*0 mono,1 stereo,2 5.1,3 adaptive*/)[ayushozha]. - Add (QE): signature disputed —
qeSeq.addTracks(videoCount, videoInsertIndex, audioCount, audioInsertIndex, audioMediaType/*1*/, submixCount, submixInsertIndex)(7 args) [hetpatel — with a fix comment that a 3-arg version silently inserted at the bottom shifting all indices] vsqeSeq.addTracks(video, audioStereo, audioMono, audio51[, aAdaptive])[leancoderkavy]. [hetpatel] also warns bulk addTracks can block/wedge the CEP bridge — prefer one at a time. - Remove:
seq.videoTracks.deleteTrack(index)[hetpatel] orseq.deleteVideoTrackAt(index)/deleteAudioTrackAt(index)[leancoderkavy] or QEqeSeq.removeVideoTrack(i)/removeAudioTrack(i)[ayushozha; also on our build's reflect list]. - Lock
track.setLocked(1|0)(orsetLock— [ayushozha] tries both; our build's QE reflect showssetLock); mutetrack.setMute(1|0); solo (QE audio)qeTrack.setSolo(bool); targettrack.setTargeted(bool, true); readsisLocked()/isMuted()/isTargeted(). - QE access:
qeSeq.getVideoTrackAt(i)/getAudioTrackAt(i)→ QETrack with.numItems,.getItemAt(i),.razor(...),.insert(...),.overwrite(...)(matches our reflect dump). QE item lists include Empty gap items — filteritem.type === "Clip"(confirmed by us and [hetpatel]). - Standard-DOM↔QE index correspondence: same trackIndex/clipIndex works in both DOMs — [leancoderkavy]'s whole design leans on this.
Standard DOM (mostly untested by us — our QE-only findings in QE_DOM_NOTES.md):
- Insert (ripple):
seq.insertClip(projectItem, startTicksString, videoTrackIndex, audioTrackIndex). - Overwrite:
seq.overwriteClip(projectItem, start, vIdx, aIdx).-1does NOT suppress the non-target side on PPro 2026 — corrected 2026-07-26 by live probe, against [leancoderkavy]'s "pass -1 as the non-target index to place only the video or only the audio side". Placing video withaIdx = -1succeeds and Premiere still auto-links the source's audio onto A1, overwriting (and destroying) whatever was there. Two other routes were tested and behave the same: the source-monitor path (overwrite-from-source), and untargeting A1 / targeting A2 first — the linked audio lands on A1 regardless of track targeting. There appears to be no API way to place video without the linked audio arriving; strip the audio from the source file instead (ffmpeg -i in.mp4 -c copy -an out.mp4).overwrite-clip-atremoves the audio it ADDS, but cannot restore what the overwrite DESTROYED. Track-level:track.overwriteClip(projectItem, seconds)[hetpatel: seconds number; ayushozha: Time object]. (QE variantqeTrack.overwrite(projectItem)confirmed working on our build with a single arg.) ⚠️ Auto-linked audio trap: overwriting a video clip whose source has audio silently places the linked audio too and can destroy existing audio on that track [hetpatel — "silent overlay PCM overwrote founder voice on A1"]. Their fix: after overwrite, scan audio tracks for clips with the sameprojectItem.nodeIdwithin 0.1s andremove(false,false)them, iterating backwards (remove shifts indices).- Remove:
clip.remove(rippleBool, alignToVideoBool)— ripple=true closes the gap, false = lift. (QEitem.remove()zero-arg also confirmed working on our build.) - Move: assign
clip.start = newStartTicksString(or a Time) [leancoderkavy, ayushozha]; or relativeclip.move(shiftSeconds)[hetpatel]. Neither standard-DOM path changes track. - Move across tracks: [leancoderkavy] calls QE
qeClip.moveToTrack(targetTrackIndex)with ONE int arg — but our live test of exactly that got "Not Enough Parameters", and 3+ args all got "Illegal Parameter type". Version drift or wrong in their repo; on our build the working alternative is remove +qeTrack.overwrite(projectItem)(or standard-DOM overwriteClip). - Trim: assign
clip.inPoint/clip.outPoint(Time or ticks string); ripple/roll/slip/slide have no standard-DOM API — [ayushozha] hand-emulates by shifting later clips (fragile, desyncs linked audio). QE has nativeqeClip.rippleDelete()/roll(offsetTicksStr)/slide(...)/slip(...)(all on our build's reflect list too). - Split/razor: QE only —
qeTrack.razor(ticksString)[leancoderkavy, ayushozha] orqeTrack.razor("HH:MM:SS:FF")[hetpatel]. Sequence must be active first. Standard-DOM "fallback" (truncatingclip.end) is destructive — don't. - Speed: QE
qeClip.setSpeed(percent, ripple, reverse)[ayushozha 3-arg] /setSpeed(percent, maintainAudioBool)[hetpatel 2-arg] — another version-drift signature; readclip.getSpeed()(standard), negative/isSpeedReversed()= reversed. Freeze frame = Time Remapping "Speed" keyframes at value 0. - Link / unlink A+V (the thing QE has NO method for — standard DOM does):
- Selection-based: select clips (
clip.setSelected(1, true)), thenseq.linkSelection()/seq.unlinkSelection()[hetpatel, leancoderkavy]. - Direct (claimed):
vClip.link(aClip)/clip.unlink()[ayushozha — unverified elsewhere; probe before trusting]. - Query:
clip.getLinkedItems()→ collection [leancoderkavy]; no is-linked property — repos detect links heuristically (same projectItem.nodeId + same start ticks across track types). - Menu fallback:
app.executeCommand(app.findMenuCommandId("Link"|"Unlink")).
- Selection-based: select clips (
- Selection:
clip.setSelected(1|0, /*updateUI*/1),clip.isSelected(),seq.getSelection()→ TrackItem array. - Enable/disable:
clip.disabled = bool[hetpatel] orclip.setDisabled(bool)[leancoderkavy] — probe which exists. - Rename on timeline: QE
qeClip.setName(x)(standardclip.name = xalso cited [hetpatel]). - Interp/blend:
qeClip.setFrameBlend(bool),qeClip.setTimeInterpolationType(0 sampling|1 blending|2 optical flow). - IDs:
clip.nodeIdis the identity key everywhere (locating clips after insert, matching linked audio, dedup). - Source monitor 3-point editing:
app.sourceMonitor.openProjectItem(item) /.getProjectItem()/.closeClip()/.closeAllClips()/.play(speed)/.getPosition(), combined with seq.insertClip/overwriteClip at the playhead. - Playback: QE
qe.startPlayback()/qe.stopPlayback(); richer [ayushozha]:qeSeq.player.play(speed)/stop()/step(±1).
Apply = QE, tune = standard DOM. The universal dance:
app.enableQE();
var fx = qe.project.getVideoEffectByName("Gaussian Blur"); // getAudioEffectByName for audio
qeClip.addVideoEffect(fx); // addAudioEffect
// back in standard DOM — new component is appended LAST:
var comp = clip.components[clip.components.numItems - 1];- Enumeration:
qe.project.getVideoEffectList()/getAudioEffectList()/ getVideoTransitionList()/getAudioTransitionList(). These return a plain array of STRINGS, not a collection of objects: read.lengthand use the entry itself as the name. CORRECTED 2026-07-24 — the earlier note here ("PPro 2026: getVideoTransitionList() returns empty" [leancoderkavy]) was our own bug, not a Premiere quirk: both list commands read.numItems(undefined on an array, so the loop never ran) andentry.name(undefined on a string). Measured on PPro 2026: 110 video transitions and 3 audio ("Constant Power", "Constant Gain", "Exponential Fade"). - Audio transitions CAN be added (corrects an earlier "video-only"
assumption):
addTransitionexists on QE audio clip objects and the headlineqeClip.addTransition(tr, atEnd, duration)form verifies first try, with the transition resolved viaqe.project.getAudioTransitionByName("Constant Power"). Live-applied 2026-07-24 across 98 clips on one audio track. - addTransition's duration argument is
"SS.FF"(seconds.frames), NOT decimal seconds. Measured at 25fps:"0.08"produced an 8-frame (0.32s) transition,"0.02"produced exactly 2 frames,"1.0"produced 25 frames. PassingString(durationSeconds)therefore silently mis-sizes any non-whole-second duration — seeppbDurationSecFramesinpanel/host/index.jsx. track.transitions[i].remove()throws "Not Enough Parameters"; the form that works isremove(false, false).- Component model:
clip.components— [0]=Motion, [1]=Opacity, [2+]=applied effects [ayushozha]; comp.displayName,.matchName(e.g.AE.ADBE Motion,AE.ADBE Opacity,audioVolume,AE.ADBE Lumetri,AE.ADBE AECrop,AE.ADBE Text),.properties(.numItems,[i],.getParamForDisplayName(name)[ayushozha]),.remove()(single-effect removal [leancoderkavy] — but [hetpatel] claims removal impossible; probe),.enabled. - Property API:
Key times: Time objects [leancoderkavy, ayushozha] or plain seconds [hetpatel] — version drift; keyframe times are in sequence time (add clip.start).
prop.getValue(); prop.setValue(value, /*updateUI*/true); // number | bool | string | [x,y] prop.setColorValue(a, r, g, b, true); // 0-255 prop.setTimeVarying(true); // REQUIRED before keyframing prop.addKey(t); prop.setValueAtKey(t, v, true); prop.getKeys(); prop.getValueAtKey(t); prop.getValueAtTime(t); prop.removeKey(t); prop.removeKeyRange(t1, t2); prop.setInterpolationTypeAtKey(t, type[, outType][, updateUI]); // 0=Linear, 4=Hold, 5=Bezier [leancoderkavy]; 0/1/2 map [ayushozha] prop.areKeyframesSupported();
- displayName matching is locale-dependent ("Nivel", "Pegel", "音量") — match exact then normalized-lowercase [hetpatel].
- Motion props: Position (
[x,y]pixels), Scale, Scale Width, Rotation (degrees), Anchor Point, Uniform Scale (0/1). Opacity props: Opacity (0-100), Blend Mode (int enum 1=Normal…22 or 27=Luminosity — the two repos disagree on the map size). - Audio Level is LINEAR AMPLITUDE, not dB: displayed 0 dB ≈ internal
0.17783 [hetpatel]. Their empirical calibration (PPro 2026 macOS):
linear = 10^((dB − 15) / 20). [leancoderkavy] uses10^(dB/20); [ayushozha] mostly passes raw dB (their docs admit it's linear). Calibrate against our own build before automating volume. - Transitions (QE only):
qe.project.getVideoTransitionByName(name), thenqeClip.addTransition(tr, atEndBool, duration, ...)— duration format disagrees:"frames:00"timecode string [hetpatel] vs seconds string"1.0"[ayushozha] vs ticks string [leancoderkavy], and arity varies (3–7 args). AlsoqeTrack.addTransition(...). Probe on our build. Removal via standard DOMtrack.transitions[i].remove(false,false).addTransition(null, true, "1.0")= default transition [ayushozha]. - Lumetri: apply by name if missing, then flat props by displayName
(Exposure, Contrast, Saturation 0-200, Temperature, Tint, ..., LUT via
"Input LUT"= path string). Names repeat across Lumetri sub-sections — first writable match wins; 0 is a legit value, don't falsy-check [leancoderkavy]. - Warp Stabilizer: apply, set "Smoothness" (num), "Method" (string enum).
qeClip.removeEffects()removes ALL effects (on our reflect list too).
- No title-creation API exists. Legacy
createNewTitleis gone; Essential Graphics titles can't be created from script. Options: captions API, MOGRT import, or render a PNG externally and import it. - MOGRT:
seq.importMGT(mogrtPath, startTicksString, vTrackIdx, aTrackIdx)→ trackItem;seq.importMGTFromLibrary(libName, mogrtName, ticks, v, a);trackItem.getMGTComponent()→ params (read AND write via setValue). - MOGRT Source Text format [hetpatel — hard-won]: value =
4-byte binary header + JSON (
mTextParamstructure). PlainsetValue("text")stores but doesn't render. Correct: strip header, JSON-parse, patchmTextParam.mStyleSheet.mText, re-prepend header, setValue. Newer AE shape: patchtextEditValue+fontTextRunLength = [text.length]. - Synthetic:
app.project.newBarsAndTone(w, h, timebaseStr, name); adjustment layer — PPro 2026:qe.project.newAdjustmentLayer()(legacyqeSeq.addAdjustmentLayer(track)removed in 2026); QEnewColorMatte(r,g,b,name),newTransparentVideo(name,w,h,dur),qeTrack.insertBlackVideo(startSecsStr, durSecsStr),qeSeq.insertBarsAndTone(wStr, hStr, durStr).
- Lifecycle:
app.newProject(path)(flaky per [leancoderkavy] research),app.openDocument(path),app.project.save()/saveAs(path),project.closeDocument(save01, prompt01),app.isDocumentOpen(),app.version/.build/.path. - Import:
app.project.importFiles([fsNames], /*suppressUI*/true, targetBin, /*asNumberedStills*/false)— return value unreliable; diffnodeIdsnapshots before/after and matchgetMediaPath()[hetpatel]. 4th arg true = image sequence as one clip. Blocking modal dialogs (e.g. unsupported format) freeze the bridge — pre-filter extensions. - AE:
app.project.importAEComps(aep, [names], bin)/importAllAEComps; BridgeTalk to drive AE itself (bt.target = "aftereffects"). - Bins:
parentBin.createBin(name),bin.renameBin(x),bin.deleteBin(),app.project.deleteAsset(item),item.moveBin(destBin),rootItem.createSmartBin(name, query),app.project.getInsertionBin(). Type:item.type === ProjectItemType.BIN(2); 1=clip, 4=file;item.isSequence(),item.treePath. - Lookup: recursive
rootItem.childrenwalk by nodeId/name;rootItem.findItemsMatchingMediaPath(pathSubstr)→ array. - ProjectItem:
getMediaPath(),canChangeMediaPath(),changeMediaPath(newPath, /*overrideChecks*/true),refreshMedia(),setOffline()/isOffline(),setScaleToFrameSize(),setOverrideFrameRate(fps),setOverridePixelAspectRatio(num, den),setStartTime(ticksStr),item.name = x,setInPoint(ticks, mediaType /*1 video, 2 audio, 4 all*/)/setOutPoint/clearInPoint()/clearOutPoint(),createSubClip(name, inTicks, outTicks, hardBounds01, takeVideo01, takeAudio01)(arg order of the last two flipped between repos — probe),item.setAudioGain(dB)[ayushozha], footage interpretation (getFootageInterpretation()→ mutate.frameRate/.pixelAspectRatio/ .fieldType/.alphaUsage→setFootageInterpretation(i)). - Proxies:
hasProxy()/canProxy()/getProxyPath()/attachProxy(path, isHiRes01)/ detachProxy();item.createProxy(presetPath)claimed by [ayushozha] but [leancoderkavy] says no createProxy exists (AME route only) — probe. Global:app.project.setProxyEnabled(bool)or QEtoggleProxies. app.project.consolidateDuplicates(); Project Manager viaapp.projectManagerattrs +pm.process(app.project).
- Markers (same API on
seq.markers,clip.markers,item.getMarkers()):markers.createMarker(SECONDS)→ set.name/.comments/.type ("Comment"|"Chapter"|"Segmentation"|"WebLink")/.end = seconds/ .setColorByIndex(0-7); iterategetFirstMarker()/getNextMarker(m)(no indexing [ayushozha] — though [hetpatel] indexes+deleteMarker(i));markers.deleteMarker(m); id =marker.guid. - Labels:
item.setColorLabel(0-15)(0=Violet … 15=Yellow). - Metadata:
item.getProjectMetadata()/setProjectMetadata(value, [fieldPath]),getXMPMetadata()/setXMPMetadata(xml)(XMPMeta via AdobeXMPScript),app.project.addPropertyToProjectMetadataSchema(name, label, type). - Undo:
app.project.undo()/redo()[ayushozha] or QEqe.project.undo()(redo QE-only per [leancoderkavy]). No undo grouping — every op is its own undo step. - Workspaces:
app.getWorkspaces()/app.setWorkspace(name). - Prefs:
app.properties.getProperty(key)/setProperty(key, val, persist)— keys like "Default Transition Duration", "AutoSave.Enabled". - QE menu/command escape hatch [ayushozha]:
qe.executeCommand("cmd.paste"|"cmd.undo"|"cmd.linkedSelection"|...),qe.project.getMenuByName("File")→ walk →item.execute(),qe.simulateKeyPress(key, ctrl, shift, alt). Standard-DOM equivalent:app.executeCommand(app.findMenuCommandId("Undo"))— unavailable in some CEP contexts. - Events:
app.bind("onActiveSequenceChanged"|"onItemsAddedToProject"|..., fn);app.setSDKEventMessage(msg, level)posts to Premiere's Events panel. - Shell:
app.system(cmd);File.execute().
- Synchronize/merge clips by audio waveform — no API (matches our earlier conclusion).
- Reading caption track contents — write-only API.
- Creating Essential Graphics titles from scratch.
- AME render-progress introspection — fire and forget.
- Undo grouping / transactional edits.
- Native ripple/roll/slip/slide in the standard DOM (QE only).
- Multicam creation from script (flags/angles partially QE-readable).
| [hetpatel] | [leancoderkavy] | [ayushozha] | ours | |
|---|---|---|---|---|
| Channel | temp-dir file queue, 150/250ms polls | temp-dir file queue, 100/200ms polls | WebSocket server (port 9801) inside CEP panel | HTTP server (port 47823) inside CEP panel |
| Script style | inline ExtendScript strings per tool | inline strings, ES3 builder | 1,020 pre-loaded named JSX functions + evalCommand dispatcher | pre-loaded named ppb_ functions |
Notes for us: our HTTP-server-in-panel approach is architecturally closest
to [ayushozha] (theirs proves a socket server inside CEP scales to 1,000+
functions). Their lazy $.evalFile trick matters if index.jsx ever gets
too big to load eagerly. All repos wrap every function to return JSON
strings and treat "EvalScript error." as the failure sentinel — we
already do both. ES3 only: no let/const/arrow/template literals in JSX.
qeClip.moveToTrack(int)works in [leancoderkavy]'s code but fails ("Not Enough Parameters") on our build — do not assume repo code runs on our version.qeTrack.overwrite(projectItem)single-arg: confirmed by us; repos use the standard-DOMtrack.overwriteClip(projectItem, time)instead, which also offers time positioning — likely the better primitive for us.clip.remove(ripple, alignToVideo)standard DOM 2-arg matches our QE zero-argitem.remove()finding (QE default semantics unverified).- The unlink/relink we thought didn't exist: absent from QE, but
present in the standard DOM as
seq.linkSelection()/unlinkSelection()(+ selection), and possiblyclip.link()/unlink()— this reopens the original "unlink → replace audio → relink" workflow as fully scriptable.
Two more repos were evaluated as fix-sources for the broken commands in BUILD_FINDINGS.md, and antipaster's leads were live-probed the same day (results in BUILD_FINDINGS.md's corrections section, which supersedes everything here).
Original code (not a fork of the other three). MCP → WebSocket :8097 →
CEP panel → evalScript; 12 domain .jsx modules preloaded via
$.evalFile; Windows installer; "tested on 2026". Claims vs our build:
| Claim | Our live result |
|---|---|
qe.project.undoStackManager.undo()/.redo() (playback.jsx) |
does NOT exist on our build — but probing around it surfaced qe.project.undoStackIndex(), which led to the undo/redo correction |
qeClip.moveToTrack(vOffset, aOffset, "0", false) — relative offsets (timeline.jsx) |
WORKS — true lossless move |
qe.project.newBarsAndTone(w, h) 2-arg (graphics.jsx) |
WORKS |
DOM clip.setSpeed(str, "ticks", rev, pitch) 4-arg (timeline.jsx) |
DOM clips have no setSpeed here — but the qeClip 5-arg variant they use for freeze-frames led to the working signature |
qe.project.newAdjustmentLayer(name, w, h) (swallowed catch + "drag it yourself" note) |
still absent (typeof undefined) |
Track-object seq.videoTracks[i].insertClip(item, TimeObject) |
WORKS and honors the track index |
seq.setWorkAreaInPoint/OutPoint wrapped in "not available in this version" catches |
confirms our work-area finding |
Removing one effect via components[i].remove() (naive) |
nothing new — still a silent no-op here |
clip.markers.createMarker() (naive) |
nothing new — clip.markers still undefined here |
ARM64 note: Premiere 2026 ARM64's ExtendScript lacks a native JSON global (they ship a json2 polyfill) |
not yet relevant on this Intel-runtime install, but remember if the panel ever misbehaves on Apple Silicon |
They avoided entirely (no code at all): roll edit, range lift/extract,
setSettings(), poster frames. nestClips is an explicit "use the
Premiere UI" punt.
Hollow shell; do not revisit. The MCP server proxies everything to an
HTTP server (server.cjs) that was gitignored and never committed;
the Node→ExtendScript bridge is a stub that fakes success
(runExtendScriptFile.js); the only real ExtendScript is a 34-line trim
script with a classic Time-object-copy no-op bug. None of our 14 broken
areas is even attempted.
src/tools/expanded.ts (2026-06-29) registers ~178 tools but implements
~77 — the rest hit a default: branch returning fake success. Its
menu-command escape hatch (app.findMenuCommandId + app.executeCommand
for Undo/Lift/Extract) is unavailable in our CEP context (both
undefined, as is qe.executeCommand). Its addToTimeline
independently converged on the same track-object overwriteClip pattern
antipaster uses, plus a linked-audio cleanup scan (remove the
auto-placed audio counterpart, iterating backwards) worth keeping in
mind.