premiere-cli 0.2.0__py3-none-any.whl
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.
- premiere_cli/__init__.py +11 -0
- premiere_cli/cli.py +3664 -0
- premiere_cli/log.py +66 -0
- premiere_cli/panel/.debug +8 -0
- premiere_cli/panel/CSXS/manifest.xml +52 -0
- premiere_cli/panel/css/style.css +53 -0
- premiere_cli/panel/host/commands/add-adjustment-layer.jsx +190 -0
- premiere_cli/panel/host/commands/add-audio-keyframes.jsx +176 -0
- premiere_cli/panel/host/commands/add-custom-metadata-field.jsx +63 -0
- premiere_cli/panel/host/commands/add-keyframe.jsx +181 -0
- premiere_cli/panel/host/commands/add-marker-to-project-item.jsx +194 -0
- premiere_cli/panel/host/commands/add-marker.jsx +129 -0
- premiere_cli/panel/host/commands/add-text-overlay.jsx +101 -0
- premiere_cli/panel/host/commands/add-to-render-queue.jsx +120 -0
- premiere_cli/panel/host/commands/add-to-timeline.jsx +300 -0
- premiere_cli/panel/host/commands/add-track.jsx +188 -0
- premiere_cli/panel/host/commands/add-transition.jsx +134 -0
- premiere_cli/panel/host/commands/adjust-audio-levels.jsx +132 -0
- premiere_cli/panel/host/commands/apply-audio-effect.jsx +118 -0
- premiere_cli/panel/host/commands/apply-effect.jsx +122 -0
- premiere_cli/panel/host/commands/apply-lut.jsx +107 -0
- premiere_cli/panel/host/commands/attach-custom-property.jsx +66 -0
- premiere_cli/panel/host/commands/auto-reframe-sequence.jsx +124 -0
- premiere_cli/panel/host/commands/batch-add-transitions.jsx +158 -0
- premiere_cli/panel/host/commands/batch-apply-effect.jsx +182 -0
- premiere_cli/panel/host/commands/batch-rename-clips.jsx +170 -0
- premiere_cli/panel/host/commands/batch-set-clips-enabled.jsx +145 -0
- premiere_cli/panel/host/commands/clear-item-in-out.jsx +117 -0
- premiere_cli/panel/host/commands/clear-sequence-in-out.jsx +100 -0
- premiere_cli/panel/host/commands/close-all-source-clips.jsx +32 -0
- premiere_cli/panel/host/commands/close-sequence.jsx +80 -0
- premiere_cli/panel/host/commands/close-source-monitor.jsx +32 -0
- premiere_cli/panel/host/commands/color-correct.jsx +164 -0
- premiere_cli/panel/host/commands/copy-effect-values.jsx +183 -0
- premiere_cli/panel/host/commands/copy-effects-between-clips.jsx +234 -0
- premiere_cli/panel/host/commands/create-bars-and-tone.jsx +152 -0
- premiere_cli/panel/host/commands/create-bin.jsx +76 -0
- premiere_cli/panel/host/commands/create-caption-track.jsx +159 -0
- premiere_cli/panel/host/commands/create-sequence-from-clips.jsx +170 -0
- premiere_cli/panel/host/commands/create-sequence-from-preset.jsx +90 -0
- premiere_cli/panel/host/commands/create-sequence.jsx +166 -0
- premiere_cli/panel/host/commands/create-smart-bin.jsx +80 -0
- premiere_cli/panel/host/commands/create-subclip.jsx +248 -0
- premiere_cli/panel/host/commands/create-subsequence.jsx +136 -0
- premiere_cli/panel/host/commands/debug-qe-inspect.jsx +248 -0
- premiere_cli/panel/host/commands/debug-qe-try-mutate.jsx +305 -0
- premiere_cli/panel/host/commands/delete-marker.jsx +178 -0
- premiere_cli/panel/host/commands/deselect-all-clips.jsx +98 -0
- premiere_cli/panel/host/commands/detach-proxy.jsx +116 -0
- premiere_cli/panel/host/commands/duplicate-clip.jsx +140 -0
- premiere_cli/panel/host/commands/duplicate-sequence.jsx +150 -0
- premiere_cli/panel/host/commands/encode-file.jsx +86 -0
- premiere_cli/panel/host/commands/encode-project-item.jsx +145 -0
- premiere_cli/panel/host/commands/export-aaf.jsx +77 -0
- premiere_cli/panel/host/commands/export-fcp-xml.jsx +65 -0
- premiere_cli/panel/host/commands/export-frame.jsx +381 -0
- premiere_cli/panel/host/commands/export-omf.jsx +95 -0
- premiere_cli/panel/host/commands/export-sequence-as-project.jsx +65 -0
- premiere_cli/panel/host/commands/export-sequence.jsx +100 -0
- premiere_cli/panel/host/commands/extract-audio-track.jsx +181 -0
- premiere_cli/panel/host/commands/extract-selection.jsx +113 -0
- premiere_cli/panel/host/commands/find-items-by-media-path.jsx +112 -0
- premiere_cli/panel/host/commands/freeze-frame.jsx +215 -0
- premiere_cli/panel/host/commands/get-active-sequence.jsx +103 -0
- premiere_cli/panel/host/commands/get-all-project-paths.jsx +72 -0
- premiere_cli/panel/host/commands/get-bin-contents.jsx +176 -0
- premiere_cli/panel/host/commands/get-clip-adjustment-layer.jsx +83 -0
- premiere_cli/panel/host/commands/get-clip-at-playhead.jsx +99 -0
- premiere_cli/panel/host/commands/get-clip-at-position.jsx +120 -0
- premiere_cli/panel/host/commands/get-clip-links.jsx +135 -0
- premiere_cli/panel/host/commands/get-clip-markers.jsx +144 -0
- premiere_cli/panel/host/commands/get-clip-speed.jsx +69 -0
- premiere_cli/panel/host/commands/get-color-label.jsx +89 -0
- premiere_cli/panel/host/commands/get-color-space.jsx +99 -0
- premiere_cli/panel/host/commands/get-duplicate-media.jsx +78 -0
- premiere_cli/panel/host/commands/get-effect-properties.jsx +129 -0
- premiere_cli/panel/host/commands/get-encoder-presets.jsx +131 -0
- premiere_cli/panel/host/commands/get-export-file-extension.jsx +56 -0
- premiere_cli/panel/host/commands/get-footage-interpretation.jsx +113 -0
- premiere_cli/panel/host/commands/get-full-clip-info.jsx +162 -0
- premiere_cli/panel/host/commands/get-full-project-overview.jsx +193 -0
- premiere_cli/panel/host/commands/get-full-sequence-info.jsx +184 -0
- premiere_cli/panel/host/commands/get-graphics-white-luminance.jsx +31 -0
- premiere_cli/panel/host/commands/get-insertion-bin.jsx +35 -0
- premiere_cli/panel/host/commands/get-item-metadata.jsx +94 -0
- premiere_cli/panel/host/commands/get-keyframes.jsx +158 -0
- premiere_cli/panel/host/commands/get-mogrt-component.jsx +111 -0
- premiere_cli/panel/host/commands/get-next-edit-point.jsx +131 -0
- premiere_cli/panel/host/commands/get-offline-media.jsx +67 -0
- premiere_cli/panel/host/commands/get-open-projects.jsx +50 -0
- premiere_cli/panel/host/commands/get-premiere-state.jsx +164 -0
- premiere_cli/panel/host/commands/get-project-info.jsx +63 -0
- premiere_cli/panel/host/commands/get-project-item-info.jsx +247 -0
- premiere_cli/panel/host/commands/get-project-panel-metadata.jsx +30 -0
- premiere_cli/panel/host/commands/get-qe-clip-info.jsx +198 -0
- premiere_cli/panel/host/commands/get-render-queue-status.jsx +35 -0
- premiere_cli/panel/host/commands/get-sequence-count.jsx +24 -0
- premiere_cli/panel/host/commands/get-sequence-in-out.jsx +76 -0
- premiere_cli/panel/host/commands/get-sequence-markers-by-type.jsx +89 -0
- premiere_cli/panel/host/commands/get-source-monitor-info.jsx +34 -0
- premiere_cli/panel/host/commands/get-source-monitor-position.jsx +30 -0
- premiere_cli/panel/host/commands/get-target-tracks.jsx +76 -0
- premiere_cli/panel/host/commands/get-timeline-gaps.jsx +124 -0
- premiere_cli/panel/host/commands/get-timeline-summary.jsx +154 -0
- premiere_cli/panel/host/commands/get-total-clip-count.jsx +67 -0
- premiere_cli/panel/host/commands/get-track-info.jsx +117 -0
- premiere_cli/panel/host/commands/get-unused-media.jsx +99 -0
- premiere_cli/panel/host/commands/get-used-media-report.jsx +125 -0
- premiere_cli/panel/host/commands/get-value-at-time.jsx +135 -0
- premiere_cli/panel/host/commands/get-version-info.jsx +33 -0
- premiere_cli/panel/host/commands/get-work-area.jsx +52 -0
- premiere_cli/panel/host/commands/get-workspaces.jsx +29 -0
- premiere_cli/panel/host/commands/get-xmp-metadata.jsx +107 -0
- premiere_cli/panel/host/commands/import-ae-comps.jsx +129 -0
- premiere_cli/panel/host/commands/import-fcp-xml.jsx +103 -0
- premiere_cli/panel/host/commands/import-folder.jsx +193 -0
- premiere_cli/panel/host/commands/import-image-sequence.jsx +171 -0
- premiere_cli/panel/host/commands/import-media.jsx +217 -0
- premiere_cli/panel/host/commands/import-mogrt-from-library.jsx +105 -0
- premiere_cli/panel/host/commands/import-mogrt.jsx +111 -0
- premiere_cli/panel/host/commands/import-sequences-from-project.jsx +80 -0
- premiere_cli/panel/host/commands/insert-from-source.jsx +162 -0
- premiere_cli/panel/host/commands/inspect-dom-object.jsx +162 -0
- premiere_cli/panel/host/commands/invert-selection.jsx +114 -0
- premiere_cli/panel/host/commands/is-work-area-enabled.jsx +88 -0
- premiere_cli/panel/host/commands/lift-selection.jsx +106 -0
- premiere_cli/panel/host/commands/link-selection.jsx +111 -0
- premiere_cli/panel/host/commands/list-available-audio-effects.jsx +33 -0
- premiere_cli/panel/host/commands/list-available-audio-transitions.jsx +35 -0
- premiere_cli/panel/host/commands/list-available-effects.jsx +33 -0
- premiere_cli/panel/host/commands/list-available-transitions.jsx +74 -0
- premiere_cli/panel/host/commands/list-markers.jsx +69 -0
- premiere_cli/panel/host/commands/list-project-items.jsx +105 -0
- premiere_cli/panel/host/commands/lock-track.jsx +114 -0
- premiere_cli/panel/host/commands/manage-proxies.jsx +176 -0
- premiere_cli/panel/host/commands/match-frame.jsx +141 -0
- premiere_cli/panel/host/commands/move-clip-to-track.jsx +220 -0
- premiere_cli/panel/host/commands/move-clip.jsx +97 -0
- premiere_cli/panel/host/commands/move-items-to-bin.jsx +194 -0
- premiere_cli/panel/host/commands/move-playhead-to-edit.jsx +174 -0
- premiere_cli/panel/host/commands/move-playhead.jsx +137 -0
- premiere_cli/panel/host/commands/nest-clips.jsx +179 -0
- premiere_cli/panel/host/commands/open-in-source.jsx +117 -0
- premiere_cli/panel/host/commands/open-project.jsx +61 -0
- premiere_cli/panel/host/commands/overwrite-clip-at.jsx +350 -0
- premiere_cli/panel/host/commands/overwrite-from-source.jsx +150 -0
- premiere_cli/panel/host/commands/play-source-monitor.jsx +36 -0
- premiere_cli/panel/host/commands/play-timeline.jsx +24 -0
- premiere_cli/panel/host/commands/razor-all-tracks.jsx +171 -0
- premiere_cli/panel/host/commands/redo.jsx +69 -0
- premiere_cli/panel/host/commands/refresh-media.jsx +101 -0
- premiere_cli/panel/host/commands/relink-media.jsx +132 -0
- premiere_cli/panel/host/commands/remove-all-effects.jsx +114 -0
- premiere_cli/panel/host/commands/remove-effect-by-name.jsx +143 -0
- premiere_cli/panel/host/commands/remove-effect.jsx +122 -0
- premiere_cli/panel/host/commands/remove-from-timeline.jsx +93 -0
- premiere_cli/panel/host/commands/remove-keyframe-range.jsx +173 -0
- premiere_cli/panel/host/commands/remove-keyframe.jsx +192 -0
- premiere_cli/panel/host/commands/remove-selected-clips.jsx +126 -0
- premiere_cli/panel/host/commands/remove-track-intervals.jsx +254 -0
- premiere_cli/panel/host/commands/remove-transition.jsx +164 -0
- premiere_cli/panel/host/commands/rename-bin.jsx +88 -0
- premiere_cli/panel/host/commands/rename-clip.jsx +127 -0
- premiere_cli/panel/host/commands/rename-project-item.jsx +111 -0
- premiere_cli/panel/host/commands/rename-track.jsx +84 -0
- premiere_cli/panel/host/commands/replace-clip-media.jsx +140 -0
- premiere_cli/panel/host/commands/replace-clip.jsx +188 -0
- premiere_cli/panel/host/commands/reverse-clip.jsx +187 -0
- premiere_cli/panel/host/commands/ripple-delete-clip.jsx +156 -0
- premiere_cli/panel/host/commands/roll-edit.jsx +162 -0
- premiere_cli/panel/host/commands/save-project-as.jsx +55 -0
- premiere_cli/panel/host/commands/save-project.jsx +57 -0
- premiere_cli/panel/host/commands/scene-edit-detection.jsx +128 -0
- premiere_cli/panel/host/commands/search-project-items.jsx +180 -0
- premiere_cli/panel/host/commands/select-all-clips.jsx +107 -0
- premiere_cli/panel/host/commands/select-clips-by-color.jsx +142 -0
- premiere_cli/panel/host/commands/select-clips-by-name.jsx +133 -0
- premiere_cli/panel/host/commands/select-clips-in-range.jsx +133 -0
- premiere_cli/panel/host/commands/select-disabled-clips.jsx +127 -0
- premiere_cli/panel/host/commands/select-project-item.jsx +115 -0
- premiere_cli/panel/host/commands/set-active-project.jsx +184 -0
- premiere_cli/panel/host/commands/set-active-sequence.jsx +87 -0
- premiere_cli/panel/host/commands/set-all-tracks-targeted.jsx +112 -0
- premiere_cli/panel/host/commands/set-anti-alias-quality.jsx +125 -0
- premiere_cli/panel/host/commands/set-blend-mode.jsx +111 -0
- premiere_cli/panel/host/commands/set-clip-anchor-point.jsx +102 -0
- premiere_cli/panel/host/commands/set-clip-enabled.jsx +127 -0
- premiere_cli/panel/host/commands/set-clip-opacity.jsx +102 -0
- premiere_cli/panel/host/commands/set-clip-pan.jsx +107 -0
- premiere_cli/panel/host/commands/set-clip-position.jsx +103 -0
- premiere_cli/panel/host/commands/set-clip-properties.jsx +221 -0
- premiere_cli/panel/host/commands/set-clip-rotation.jsx +102 -0
- premiere_cli/panel/host/commands/set-clip-scale.jsx +102 -0
- premiere_cli/panel/host/commands/set-clip-selection.jsx +144 -0
- premiere_cli/panel/host/commands/set-clip-speed.jsx +160 -0
- premiere_cli/panel/host/commands/set-clip-volume.jsx +128 -0
- premiere_cli/panel/host/commands/set-color-label.jsx +123 -0
- premiere_cli/panel/host/commands/set-color-value.jsx +155 -0
- premiere_cli/panel/host/commands/set-effect-property.jsx +124 -0
- premiere_cli/panel/host/commands/set-footage-interpretation.jsx +173 -0
- premiere_cli/panel/host/commands/set-frame-blend.jsx +145 -0
- premiere_cli/panel/host/commands/set-graphics-white-luminance.jsx +73 -0
- premiere_cli/panel/host/commands/set-item-in-out.jsx +180 -0
- premiere_cli/panel/host/commands/set-item-metadata.jsx +134 -0
- premiere_cli/panel/host/commands/set-item-offline.jsx +109 -0
- premiere_cli/panel/host/commands/set-item-start-time.jsx +139 -0
- premiere_cli/panel/host/commands/set-keyframe-interpolation.jsx +186 -0
- premiere_cli/panel/host/commands/set-override-frame-rate.jsx +123 -0
- premiere_cli/panel/host/commands/set-override-pixel-aspect-ratio.jsx +129 -0
- premiere_cli/panel/host/commands/set-poster-frame.jsx +192 -0
- premiere_cli/panel/host/commands/set-project-panel-metadata.jsx +69 -0
- premiere_cli/panel/host/commands/set-scale-to-frame-size.jsx +101 -0
- premiere_cli/panel/host/commands/set-scale-width-height.jsx +140 -0
- premiere_cli/panel/host/commands/set-sequence-in-out.jsx +65 -0
- premiere_cli/panel/host/commands/set-sequence-settings.jsx +184 -0
- premiere_cli/panel/host/commands/set-source-in-out.jsx +98 -0
- premiere_cli/panel/host/commands/set-target-track.jsx +91 -0
- premiere_cli/panel/host/commands/set-time-interpolation.jsx +141 -0
- premiere_cli/panel/host/commands/set-track-mute.jsx +91 -0
- premiere_cli/panel/host/commands/set-track-visibility.jsx +94 -0
- premiere_cli/panel/host/commands/set-transcode-on-ingest.jsx +78 -0
- premiere_cli/panel/host/commands/set-uniform-scale.jsx +104 -0
- premiere_cli/panel/host/commands/set-work-area.jsx +76 -0
- premiere_cli/panel/host/commands/set-workspace.jsx +35 -0
- premiere_cli/panel/host/commands/set-xmp-metadata.jsx +157 -0
- premiere_cli/panel/host/commands/slide-edit.jsx +162 -0
- premiere_cli/panel/host/commands/slip-edit.jsx +167 -0
- premiere_cli/panel/host/commands/split-clip.jsx +144 -0
- premiere_cli/panel/host/commands/stabilize-clip.jsx +139 -0
- premiere_cli/panel/host/commands/stop-playback.jsx +23 -0
- premiere_cli/panel/host/commands/trim-clip.jsx +111 -0
- premiere_cli/panel/host/commands/undo.jsx +114 -0
- premiere_cli/panel/host/commands/unlink-selection.jsx +106 -0
- premiere_cli/panel/host/commands/unnest-sequence.jsx +207 -0
- premiere_cli/panel/host/commands/update-marker.jsx +120 -0
- premiere_cli/panel/host/index.jsx +1227 -0
- premiere_cli/panel/host/presets/default-4k-25fps.sqpreset +63 -0
- premiere_cli/panel/index.html +16 -0
- premiere_cli/panel/js/cs-interface.js +11 -0
- premiere_cli/panel/js/main.js +409 -0
- premiere_cli/panel_install.py +220 -0
- premiere_cli-0.2.0.dist-info/METADATA +127 -0
- premiere_cli-0.2.0.dist-info/RECORD +247 -0
- premiere_cli-0.2.0.dist-info/WHEEL +5 -0
- premiere_cli-0.2.0.dist-info/entry_points.txt +3 -0
- premiere_cli-0.2.0.dist-info/licenses/LICENSE +428 -0
- premiere_cli-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Command: move-clip-to-track → ppb_moveClipToTrack
|
|
2
|
+
// Loaded lazily by ppb_dispatch (host/index.jsx) on first use, into the
|
|
3
|
+
// same global ExtendScript context — shared helpers (findSequenceByName,
|
|
4
|
+
// ensureQEEnabled, activateSequenceForQE, resolveTimelineClip,
|
|
5
|
+
// resolveQeClip, serializeTrackItem, timeValueToSeconds, ...) are
|
|
6
|
+
// already defined there.
|
|
7
|
+
//
|
|
8
|
+
// Moves a clip to a different track of the SAME media type. REWRITTEN
|
|
9
|
+
// 2026-07-17 after the probe session (see BUILD_FINDINGS.md
|
|
10
|
+
// corrections): the working signature on this build — found in the
|
|
11
|
+
// antipaster/Adobe-Premiere-Pro-MCP repo — is the 4-arg
|
|
12
|
+
// qeClip.moveToTrack(videoTrackOffset, audioTrackOffset,
|
|
13
|
+
// timeShiftTicksString, bool)
|
|
14
|
+
// with RELATIVE track offsets (target − source) and the time shift as a
|
|
15
|
+
// ticks string ("0" = keep the clip's time position). Live-verified: it
|
|
16
|
+
// is a TRUE move — per-clip effects/keyframes survive (tested with a
|
|
17
|
+
// Gaussian Blur) and the start time is preserved. This replaces the old
|
|
18
|
+
// LOSSY remove()+overwrite() re-add workaround entirely.
|
|
19
|
+
//
|
|
20
|
+
// The video-clip form (offset in arg1) is live-verified; for audio
|
|
21
|
+
// clips the offset is assumed to belong in arg2 by symmetry — if the
|
|
22
|
+
// arg2 attempt produces no observable change, arg1 is tried as a
|
|
23
|
+
// fallback, with every attempt recorded.
|
|
24
|
+
//
|
|
25
|
+
// MUTATION RULE: verified by re-finding the clip on the TARGET track by
|
|
26
|
+
// name/media path near its original start time, plus per-track clip
|
|
27
|
+
// counts — never by trusting moveToTrack()'s return value.
|
|
28
|
+
|
|
29
|
+
function ppbMoveClipToTrack_trackCounts(trackCollection) {
|
|
30
|
+
var counts = [];
|
|
31
|
+
for (var t = 0; t < trackCollection.numTracks; t++) {
|
|
32
|
+
counts.push(trackCollection[t].clips.numItems);
|
|
33
|
+
}
|
|
34
|
+
return counts;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ppbMoveClipToTrack_findByNameNear(trackCollection, trackIndex, name, mediaPath, nearSeconds) {
|
|
38
|
+
if (trackIndex >= trackCollection.numTracks) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
var track = trackCollection[trackIndex];
|
|
42
|
+
var numClips = track.clips.numItems;
|
|
43
|
+
var best = null;
|
|
44
|
+
var bestDelta = null;
|
|
45
|
+
for (var i = 0; i < numClips; i++) {
|
|
46
|
+
var c = track.clips[i];
|
|
47
|
+
var cName = null;
|
|
48
|
+
var cPath = null;
|
|
49
|
+
try { cName = c.name; } catch (e) { cName = null; }
|
|
50
|
+
try { cPath = c.projectItem.getMediaPath(); } catch (e2) { cPath = null; }
|
|
51
|
+
if (cName === name || (mediaPath !== null && cPath === mediaPath)) {
|
|
52
|
+
var startSeconds = null;
|
|
53
|
+
try { startSeconds = timeValueToSeconds(c.start); } catch (e3) { startSeconds = null; }
|
|
54
|
+
var delta = (startSeconds !== null && nearSeconds !== null) ? Math.abs(startSeconds - nearSeconds) : 0;
|
|
55
|
+
if (best === null || delta < bestDelta) {
|
|
56
|
+
best = { clip: c, index: i };
|
|
57
|
+
bestDelta = delta;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return best;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ppb_moveClipToTrack(argsJson) {
|
|
65
|
+
try {
|
|
66
|
+
var args;
|
|
67
|
+
try {
|
|
68
|
+
args = JSON.parse(argsJson);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return JSON.stringify({ ok: false, error: "invalid args JSON: " + e.toString() });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (args.trackType !== "video" && args.trackType !== "audio") {
|
|
74
|
+
return JSON.stringify({ ok: false, error: "trackType must be \"video\" or \"audio\"" });
|
|
75
|
+
}
|
|
76
|
+
if (typeof args.trackIndex !== "number" || args.trackIndex < 0 || Math.floor(args.trackIndex) !== args.trackIndex) {
|
|
77
|
+
return JSON.stringify({ ok: false, error: "trackIndex must be a non-negative integer" });
|
|
78
|
+
}
|
|
79
|
+
if (typeof args.clipIndex !== "number" || args.clipIndex < 0 || Math.floor(args.clipIndex) !== args.clipIndex) {
|
|
80
|
+
return JSON.stringify({ ok: false, error: "clipIndex must be a non-negative integer" });
|
|
81
|
+
}
|
|
82
|
+
if (typeof args.targetTrackIndex !== "number" || args.targetTrackIndex < 0 || Math.floor(args.targetTrackIndex) !== args.targetTrackIndex) {
|
|
83
|
+
return JSON.stringify({ ok: false, error: "targetTrackIndex must be a non-negative integer" });
|
|
84
|
+
}
|
|
85
|
+
if (args.targetTrackIndex === args.trackIndex) {
|
|
86
|
+
return JSON.stringify({ ok: false, error: "targetTrackIndex is the same as trackIndex — nothing to move" });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!app.project) {
|
|
90
|
+
return JSON.stringify({ ok: false, error: "no project open" });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
var seq = null;
|
|
94
|
+
if (args.sequenceName && typeof args.sequenceName === "string") {
|
|
95
|
+
seq = findSequenceByName(args.sequenceName);
|
|
96
|
+
if (!seq) {
|
|
97
|
+
return JSON.stringify({ ok: false, error: "no sequence named \"" + args.sequenceName + "\" is open" });
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
seq = app.project.activeSequence;
|
|
101
|
+
if (!seq) {
|
|
102
|
+
return JSON.stringify({ ok: false, error: "no active sequence, and no sequenceName given" });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var trackCollection = args.trackType === "video" ? seq.videoTracks : seq.audioTracks;
|
|
107
|
+
if (args.targetTrackIndex >= trackCollection.numTracks) {
|
|
108
|
+
return JSON.stringify({
|
|
109
|
+
ok: false,
|
|
110
|
+
error: "targetTrackIndex " + args.targetTrackIndex + " is out of range — sequence has " + trackCollection.numTracks + " " + args.trackType + " track(s)"
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
var resolved = resolveTimelineClip(seq, args.trackType, args.trackIndex, args.clipIndex);
|
|
115
|
+
if (resolved.error) {
|
|
116
|
+
return JSON.stringify({ ok: false, error: resolved.error });
|
|
117
|
+
}
|
|
118
|
+
var before = serializeTrackItem(resolved.clip, args.trackIndex, args.clipIndex);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
ensureQEEnabled();
|
|
122
|
+
activateSequenceForQE(seq);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
return JSON.stringify({ ok: false, error: "app.enableQE()/sequence activation failed: " + e.toString() });
|
|
125
|
+
}
|
|
126
|
+
if (typeof qe === "undefined" || !qe.project) {
|
|
127
|
+
return JSON.stringify({ ok: false, error: "QE DOM not available after enableQE()" });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
var qeSeq;
|
|
131
|
+
try {
|
|
132
|
+
qeSeq = qe.project.getActiveSequence();
|
|
133
|
+
} catch (e) {
|
|
134
|
+
return JSON.stringify({ ok: false, error: "qe.project.getActiveSequence() failed: " + e.toString() });
|
|
135
|
+
}
|
|
136
|
+
var qeResolved = resolveQeClip(qeSeq, args.trackType, args.trackIndex, args.clipIndex);
|
|
137
|
+
if (qeResolved.error) {
|
|
138
|
+
return JSON.stringify({ ok: false, error: qeResolved.error });
|
|
139
|
+
}
|
|
140
|
+
var qeClip = qeResolved.qeClip;
|
|
141
|
+
if (typeof qeClip.moveToTrack !== "function") {
|
|
142
|
+
return JSON.stringify({ ok: false, error: "qeClip.moveToTrack is not a function on this build" });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
var offset = args.targetTrackIndex - args.trackIndex;
|
|
146
|
+
|
|
147
|
+
// Arg order: (videoTrackOffset, audioTrackOffset, timeShiftTicks,
|
|
148
|
+
// bool). The video slot is live-verified; the audio slot is assumed
|
|
149
|
+
// by symmetry with an arg1 fallback.
|
|
150
|
+
var forms = [];
|
|
151
|
+
if (args.trackType === "video") {
|
|
152
|
+
forms.push({ label: "moveToTrack(offset, 0, '0', false)", a: [offset, 0, "0", false] });
|
|
153
|
+
} else {
|
|
154
|
+
forms.push({ label: "moveToTrack(0, offset, '0', false)", a: [0, offset, "0", false] });
|
|
155
|
+
forms.push({ label: "moveToTrack(offset, 0, '0', false) [audio fallback]", a: [offset, 0, "0", false] });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
var attempts = [];
|
|
159
|
+
var moved = false;
|
|
160
|
+
var formUsed = null;
|
|
161
|
+
for (var f = 0; f < forms.length && !moved; f++) {
|
|
162
|
+
var countsBefore = ppbMoveClipToTrack_trackCounts(trackCollection);
|
|
163
|
+
var att = { form: forms[f].label };
|
|
164
|
+
try {
|
|
165
|
+
qeClip.moveToTrack.apply(qeClip, forms[f].a);
|
|
166
|
+
att.threw = false;
|
|
167
|
+
} catch (e2) {
|
|
168
|
+
att.threw = true;
|
|
169
|
+
att.error = e2.toString();
|
|
170
|
+
}
|
|
171
|
+
var countsAfter = ppbMoveClipToTrack_trackCounts(trackCollection);
|
|
172
|
+
att.effective = false;
|
|
173
|
+
for (var t = 0; t < countsAfter.length; t++) {
|
|
174
|
+
if (countsAfter[t] !== countsBefore[t]) { att.effective = true; }
|
|
175
|
+
}
|
|
176
|
+
attempts.push(att);
|
|
177
|
+
if (att.effective) {
|
|
178
|
+
moved = true;
|
|
179
|
+
formUsed = forms[f].label;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!moved) {
|
|
184
|
+
return JSON.stringify({
|
|
185
|
+
ok: false,
|
|
186
|
+
error: "moveToTrack() produced no observable track change with any argument form tried — the clip was left in place",
|
|
187
|
+
attempts: attempts,
|
|
188
|
+
before: before
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var found = ppbMoveClipToTrack_findByNameNear(trackCollection, args.targetTrackIndex, before.name, before.mediaPath, before.startSeconds);
|
|
193
|
+
var after = found ? serializeTrackItem(found.clip, args.targetTrackIndex, found.index) : null;
|
|
194
|
+
|
|
195
|
+
var startDelta = (after && after.startSeconds !== null && before.startSeconds !== null)
|
|
196
|
+
? (after.startSeconds - before.startSeconds)
|
|
197
|
+
: null;
|
|
198
|
+
|
|
199
|
+
return JSON.stringify({
|
|
200
|
+
ok: true,
|
|
201
|
+
result: {
|
|
202
|
+
sequenceName: seq.name,
|
|
203
|
+
trackType: args.trackType,
|
|
204
|
+
sourceTrackIndex: args.trackIndex,
|
|
205
|
+
sourceClipIndex: args.clipIndex,
|
|
206
|
+
targetTrackIndex: args.targetTrackIndex,
|
|
207
|
+
formUsed: formUsed,
|
|
208
|
+
attempts: attempts,
|
|
209
|
+
before: before,
|
|
210
|
+
after: after,
|
|
211
|
+
placedOnTarget: after !== null,
|
|
212
|
+
startSecondsDelta: startDelta,
|
|
213
|
+
positionPreserved: startDelta !== null && Math.abs(startDelta) < 0.5,
|
|
214
|
+
note: "TRUE move via qeClip.moveToTrack() — per-clip effects/keyframes survive (live-verified 2026-07-17), unlike the previous remove+overwrite workaround"
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
} catch (e) {
|
|
218
|
+
return JSON.stringify({ ok: false, error: e.toString() });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Command: move-clip → ppb_moveClip
|
|
2
|
+
// Loaded lazily by ppb_dispatch (host/index.jsx) on first use, into the
|
|
3
|
+
// same global ExtendScript context — shared helpers (findSequenceByName,
|
|
4
|
+
// resolveTimelineClip, serializeTrackItem, tryTimeForms, ...) are already
|
|
5
|
+
// defined there.
|
|
6
|
+
//
|
|
7
|
+
// Moves a clip to a new ABSOLUTE start time on the SAME track via
|
|
8
|
+
// clip.start assignment (PREMIERE_API_NOTES.md: "assign clip.start =
|
|
9
|
+
// newStartTicksString ... neither standard-DOM path changes track" —
|
|
10
|
+
// this command does not attempt a cross-track move, matching that
|
|
11
|
+
// documented limitation). Key-argument form is disputed across builds
|
|
12
|
+
// (ticks string vs Time object vs raw seconds), so tryTimeForms() tries
|
|
13
|
+
// each in turn.
|
|
14
|
+
//
|
|
15
|
+
// MUTATION RULE: verified via a clip.start read-back after the
|
|
16
|
+
// assignment, compared against the requested startSeconds with a
|
|
17
|
+
// tolerance (frame-quantization is expected).
|
|
18
|
+
|
|
19
|
+
function ppb_moveClip(argsJson) {
|
|
20
|
+
try {
|
|
21
|
+
var args;
|
|
22
|
+
try {
|
|
23
|
+
args = JSON.parse(argsJson);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return JSON.stringify({ ok: false, error: "invalid args JSON: " + e.toString() });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (args.trackType !== "video" && args.trackType !== "audio") {
|
|
29
|
+
return JSON.stringify({ ok: false, error: "trackType must be \"video\" or \"audio\"" });
|
|
30
|
+
}
|
|
31
|
+
if (typeof args.trackIndex !== "number" || typeof args.clipIndex !== "number") {
|
|
32
|
+
return JSON.stringify({ ok: false, error: "trackIndex and clipIndex are required" });
|
|
33
|
+
}
|
|
34
|
+
if (typeof args.startSeconds !== "number") {
|
|
35
|
+
return JSON.stringify({ ok: false, error: "startSeconds is required" });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!app.project) {
|
|
39
|
+
return JSON.stringify({ ok: false, error: "no project open" });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var seq = null;
|
|
43
|
+
if (args.sequenceName && typeof args.sequenceName === "string") {
|
|
44
|
+
seq = findSequenceByName(args.sequenceName);
|
|
45
|
+
if (!seq) {
|
|
46
|
+
return JSON.stringify({ ok: false, error: "no sequence named \"" + args.sequenceName + "\" is open" });
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
seq = app.project.activeSequence;
|
|
50
|
+
if (!seq) {
|
|
51
|
+
return JSON.stringify({ ok: false, error: "no active sequence, and no sequenceName given" });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var resolved = resolveTimelineClip(seq, args.trackType, args.trackIndex, args.clipIndex);
|
|
56
|
+
if (resolved.error) {
|
|
57
|
+
return JSON.stringify({ ok: false, error: resolved.error });
|
|
58
|
+
}
|
|
59
|
+
var clip = resolved.clip;
|
|
60
|
+
|
|
61
|
+
var before = serializeTrackItem(clip, args.trackIndex, args.clipIndex);
|
|
62
|
+
|
|
63
|
+
var attemptResult = tryTimeForms(args.startSeconds, function (timeArg) {
|
|
64
|
+
clip.start = timeArg;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!attemptResult.success) {
|
|
68
|
+
return JSON.stringify({
|
|
69
|
+
ok: false,
|
|
70
|
+
error: "could not set clip.start with any known argument form (ticksString, Time object, seconds) — see attempts",
|
|
71
|
+
attempts: attemptResult.attempts,
|
|
72
|
+
previousValue: before
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var after = serializeTrackItem(clip, args.trackIndex, args.clipIndex);
|
|
77
|
+
var verified = after.startSeconds !== null && Math.abs(after.startSeconds - args.startSeconds) <= 0.05;
|
|
78
|
+
|
|
79
|
+
return JSON.stringify({
|
|
80
|
+
ok: true,
|
|
81
|
+
result: {
|
|
82
|
+
sequenceName: seq.name,
|
|
83
|
+
trackType: args.trackType,
|
|
84
|
+
trackIndex: args.trackIndex,
|
|
85
|
+
clipIndex: args.clipIndex,
|
|
86
|
+
requestedStartSeconds: args.startSeconds,
|
|
87
|
+
previousValue: before,
|
|
88
|
+
newValue: after,
|
|
89
|
+
verified: verified,
|
|
90
|
+
formUsed: attemptResult.formUsed,
|
|
91
|
+
attempts: attemptResult.attempts
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
} catch (e) {
|
|
95
|
+
return JSON.stringify({ ok: false, error: e.toString() });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Command: move-items-to-bin → ppb_moveItemsToBin
|
|
2
|
+
// Loaded lazily by ppb_dispatch (host/index.jsx) on first use, into the
|
|
3
|
+
// same global ExtendScript context — shared helpers (TICKS_PER_SECOND, ...)
|
|
4
|
+
// are already defined there.
|
|
5
|
+
//
|
|
6
|
+
// MERGES the reference project's two near-duplicate media.ts/track-
|
|
7
|
+
// targeting.ts tools — move_item_to_bin (single item) and
|
|
8
|
+
// move_items_to_bin (array) — into one command: pass either nodeIds (a
|
|
9
|
+
// JSON array string) OR a single nodeId/name, plus targetBinPath. Item
|
|
10
|
+
// resolution uses children-presence recursion (NOT an isBin() gate) per
|
|
11
|
+
// get-project-item-info.jsx's live-debugged finding that the ROOT item's
|
|
12
|
+
// type is ROOT, not BIN — an isBin() gate would never descend past the
|
|
13
|
+
// root. targetBinPath is a '/'-separated bin path (same convention as
|
|
14
|
+
// create-sequence's bin arg) resolved but never auto-created here — call
|
|
15
|
+
// create-bin first if it might not exist. Each move verified by re-reading
|
|
16
|
+
// the item's own treePath afterward and confirming it now starts with the
|
|
17
|
+
// target bin's treePath.
|
|
18
|
+
|
|
19
|
+
function ppbMoveItemsToBin_findByNodeId(item, nodeId, depth) {
|
|
20
|
+
if (depth > 32) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
if (item.nodeId === nodeId) {
|
|
25
|
+
return item;
|
|
26
|
+
}
|
|
27
|
+
} catch (e) {
|
|
28
|
+
// fall through
|
|
29
|
+
}
|
|
30
|
+
if (item.children && item.children.numItems > 0) {
|
|
31
|
+
for (var i = 0; i < item.children.numItems; i++) {
|
|
32
|
+
var found = ppbMoveItemsToBin_findByNodeId(item.children[i], nodeId, depth + 1);
|
|
33
|
+
if (found) {
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ppbMoveItemsToBin_findByName(item, name, depth) {
|
|
42
|
+
if (depth > 32) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
if (item.name === name) {
|
|
47
|
+
return item;
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// fall through
|
|
51
|
+
}
|
|
52
|
+
if (item.children && item.children.numItems > 0) {
|
|
53
|
+
for (var i = 0; i < item.children.numItems; i++) {
|
|
54
|
+
var found = ppbMoveItemsToBin_findByName(item.children[i], name, depth + 1);
|
|
55
|
+
if (found) {
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ppbMoveItemsToBin_resolveBinPath(binPath) {
|
|
64
|
+
var segments = binPath.split("/").filter(function (s) { return s.length > 0; });
|
|
65
|
+
if (segments.length === 0) {
|
|
66
|
+
return { error: "targetBinPath must contain at least one non-empty segment" };
|
|
67
|
+
}
|
|
68
|
+
var current = app.project.rootItem;
|
|
69
|
+
for (var i = 0; i < segments.length; i++) {
|
|
70
|
+
var found = null;
|
|
71
|
+
for (var j = 0; j < current.children.numItems; j++) {
|
|
72
|
+
var child = current.children[j];
|
|
73
|
+
if (child.name === segments[i] && typeof ProjectItemType !== "undefined" && child.type === ProjectItemType.BIN) {
|
|
74
|
+
found = child;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!found) {
|
|
79
|
+
return { error: "bin path segment \"" + segments[i] + "\" not found in \"" + binPath + "\" (target bin must already exist — call create-bin first)" };
|
|
80
|
+
}
|
|
81
|
+
current = found;
|
|
82
|
+
}
|
|
83
|
+
return { bin: current };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ppb_moveItemsToBin(argsJson) {
|
|
87
|
+
try {
|
|
88
|
+
var args;
|
|
89
|
+
try {
|
|
90
|
+
args = JSON.parse(argsJson);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return JSON.stringify({ ok: false, error: "invalid args JSON: " + e.toString() });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var identifiers = [];
|
|
96
|
+
if (typeof args.nodeIds === "string" && args.nodeIds.length > 0) {
|
|
97
|
+
var parsed;
|
|
98
|
+
try {
|
|
99
|
+
parsed = JSON.parse(args.nodeIds);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return JSON.stringify({ ok: false, error: "nodeIds must be a JSON array string: " + e.toString() });
|
|
102
|
+
}
|
|
103
|
+
if (!(parsed instanceof Array)) {
|
|
104
|
+
return JSON.stringify({ ok: false, error: "nodeIds must parse to a JSON array" });
|
|
105
|
+
}
|
|
106
|
+
for (var i = 0; i < parsed.length; i++) {
|
|
107
|
+
identifiers.push({ nodeId: parsed[i], name: null });
|
|
108
|
+
}
|
|
109
|
+
} else if (typeof args.nodeId === "string" && args.nodeId.length > 0) {
|
|
110
|
+
identifiers.push({ nodeId: args.nodeId, name: null });
|
|
111
|
+
} else if (typeof args.name === "string" && args.name.length > 0) {
|
|
112
|
+
identifiers.push({ nodeId: null, name: args.name });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (identifiers.length === 0) {
|
|
116
|
+
return JSON.stringify({ ok: false, error: "one of nodeIds (JSON array), nodeId, or name is required" });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!args.targetBinPath || typeof args.targetBinPath !== "string") {
|
|
120
|
+
return JSON.stringify({ ok: false, error: "targetBinPath is required" });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!app.project) {
|
|
124
|
+
return JSON.stringify({ ok: false, error: "no project open" });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
var binResolved = ppbMoveItemsToBin_resolveBinPath(args.targetBinPath);
|
|
128
|
+
if (binResolved.error) {
|
|
129
|
+
return JSON.stringify({ ok: false, error: binResolved.error });
|
|
130
|
+
}
|
|
131
|
+
var targetBin = binResolved.bin;
|
|
132
|
+
var targetTreePath = null;
|
|
133
|
+
try { targetTreePath = targetBin.treePath; } catch (e) { targetTreePath = null; }
|
|
134
|
+
|
|
135
|
+
var results = [];
|
|
136
|
+
var movedCount = 0;
|
|
137
|
+
|
|
138
|
+
for (var k = 0; k < identifiers.length; k++) {
|
|
139
|
+
var ident = identifiers[k];
|
|
140
|
+
var item = ident.nodeId
|
|
141
|
+
? ppbMoveItemsToBin_findByNodeId(app.project.rootItem, ident.nodeId, 0)
|
|
142
|
+
: ppbMoveItemsToBin_findByName(app.project.rootItem, ident.name, 0);
|
|
143
|
+
|
|
144
|
+
if (!item) {
|
|
145
|
+
results.push({
|
|
146
|
+
nodeId: ident.nodeId,
|
|
147
|
+
name: ident.name,
|
|
148
|
+
moved: false,
|
|
149
|
+
error: "no project item found matching the given nodeId/name"
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
var itemName = null;
|
|
155
|
+
try { itemName = item.name; } catch (e) { itemName = null; }
|
|
156
|
+
var itemNodeId = null;
|
|
157
|
+
try { itemNodeId = item.nodeId; } catch (e) { itemNodeId = null; }
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
item.moveBin(targetBin);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
results.push({ nodeId: itemNodeId, name: itemName, moved: false, error: "moveBin() failed: " + e.toString() });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
var newTreePath = null;
|
|
167
|
+
try { newTreePath = item.treePath; } catch (e) { newTreePath = null; }
|
|
168
|
+
var verified = targetTreePath !== null && newTreePath !== null && newTreePath.indexOf(targetTreePath) === 0;
|
|
169
|
+
|
|
170
|
+
results.push({
|
|
171
|
+
nodeId: itemNodeId,
|
|
172
|
+
name: itemName,
|
|
173
|
+
moved: true,
|
|
174
|
+
newTreePath: newTreePath,
|
|
175
|
+
verified: verified
|
|
176
|
+
});
|
|
177
|
+
if (verified) {
|
|
178
|
+
movedCount++;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return JSON.stringify({
|
|
183
|
+
ok: true,
|
|
184
|
+
result: {
|
|
185
|
+
targetBinPath: args.targetBinPath,
|
|
186
|
+
requestedCount: identifiers.length,
|
|
187
|
+
movedCount: movedCount,
|
|
188
|
+
results: results
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
} catch (e) {
|
|
192
|
+
return JSON.stringify({ ok: false, error: e.toString() });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Command: move-playhead-to-edit → ppb_movePlayheadToEdit
|
|
2
|
+
// Loaded lazily by ppb_dispatch (host/index.jsx) on first use, into the
|
|
3
|
+
// same global ExtendScript context — shared helpers (TICKS_PER_SECOND,
|
|
4
|
+
// findSequenceByName, ...) are already defined there.
|
|
5
|
+
//
|
|
6
|
+
// Ported from leancoderkavy's premiere-pro-mcp move_playhead_to_edit tool
|
|
7
|
+
// (utility.ts), combining its edit-point-search logic (same one backing
|
|
8
|
+
// get-next-edit-point.jsx: collect every clip start/end tick across all
|
|
9
|
+
// video/audio tracks, sort, dedupe, pick the nearest one before/after the
|
|
10
|
+
// playhead) with move-playhead.jsx's proven seek pattern for the actual
|
|
11
|
+
// move (ticksString first, Time-object fallback, recording each attempt).
|
|
12
|
+
// Standard DOM only, no QE needed.
|
|
13
|
+
//
|
|
14
|
+
// MUTATION RULE: verified by reading back seq.getPlayerPosition() after
|
|
15
|
+
// the seek and reporting it alongside the edit point that was targeted —
|
|
16
|
+
// never trusting the seek call's own (lack of a) return value.
|
|
17
|
+
|
|
18
|
+
function ppbMovePlayheadToEdit_collectPoints(seq) {
|
|
19
|
+
var points = [];
|
|
20
|
+
|
|
21
|
+
function collect(tracks) {
|
|
22
|
+
for (var t = 0; t < tracks.numTracks; t++) {
|
|
23
|
+
for (var c = 0; c < tracks[t].clips.numItems; c++) {
|
|
24
|
+
var clip = tracks[t].clips[c];
|
|
25
|
+
try { points.push(parseFloat(clip.start.ticks)); } catch (e) { /* skip */ }
|
|
26
|
+
try { points.push(parseFloat(clip.end.ticks)); } catch (e) { /* skip */ }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
collect(seq.videoTracks);
|
|
32
|
+
collect(seq.audioTracks);
|
|
33
|
+
|
|
34
|
+
points.sort(function (a, b) { return a - b; });
|
|
35
|
+
|
|
36
|
+
var unique = [];
|
|
37
|
+
for (var i = 0; i < points.length; i++) {
|
|
38
|
+
if (unique.length === 0 || points[i] !== unique[unique.length - 1]) {
|
|
39
|
+
unique.push(points[i]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return unique;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ppb_movePlayheadToEdit(argsJson) {
|
|
46
|
+
try {
|
|
47
|
+
var args;
|
|
48
|
+
try {
|
|
49
|
+
args = JSON.parse(argsJson);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return JSON.stringify({ ok: false, error: "invalid args JSON: " + e.toString() });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
var direction = args.direction === "previous" ? "previous" : "next";
|
|
55
|
+
if (args.direction !== undefined && args.direction !== null && args.direction !== "next" && args.direction !== "previous") {
|
|
56
|
+
return JSON.stringify({ ok: false, error: "direction must be \"next\" or \"previous\"" });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!app.project) {
|
|
60
|
+
return JSON.stringify({ ok: false, error: "no project open" });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var seq = null;
|
|
64
|
+
if (args.sequenceName && typeof args.sequenceName === "string") {
|
|
65
|
+
seq = findSequenceByName(args.sequenceName);
|
|
66
|
+
if (!seq) {
|
|
67
|
+
return JSON.stringify({ ok: false, error: "no sequence named \"" + args.sequenceName + "\" is open" });
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
seq = app.project.activeSequence;
|
|
71
|
+
if (!seq) {
|
|
72
|
+
return JSON.stringify({ ok: false, error: "no active sequence, and no sequenceName given" });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (app.project.activeSequence !== seq) {
|
|
77
|
+
app.project.activeSequence = seq;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var posTicks = null;
|
|
81
|
+
try {
|
|
82
|
+
posTicks = parseFloat(seq.getPlayerPosition().ticks);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return JSON.stringify({ ok: false, error: "could not read the current playhead position: " + e.toString() });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
var editPoints = ppbMovePlayheadToEdit_collectPoints(seq);
|
|
88
|
+
|
|
89
|
+
var found = null;
|
|
90
|
+
if (direction === "next") {
|
|
91
|
+
for (var i = 0; i < editPoints.length; i++) {
|
|
92
|
+
if (editPoints[i] > posTicks + 1) {
|
|
93
|
+
found = editPoints[i];
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
for (var j = editPoints.length - 1; j >= 0; j--) {
|
|
99
|
+
if (editPoints[j] < posTicks - 1) {
|
|
100
|
+
found = editPoints[j];
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (found === null) {
|
|
107
|
+
return JSON.stringify({ ok: false, error: "no " + direction + " edit point found from the current playhead position" });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var targetSeconds = found / TICKS_PER_SECOND;
|
|
111
|
+
var ticksString = String(Math.round(found));
|
|
112
|
+
var timeObj = null;
|
|
113
|
+
try {
|
|
114
|
+
timeObj = new Time();
|
|
115
|
+
timeObj.seconds = targetSeconds;
|
|
116
|
+
} catch (e) {
|
|
117
|
+
timeObj = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
var positionAttempts = [];
|
|
121
|
+
var positionSet = false;
|
|
122
|
+
try {
|
|
123
|
+
seq.setPlayerPosition(ticksString);
|
|
124
|
+
positionAttempts.push({ form: "ticksString", success: true });
|
|
125
|
+
positionSet = true;
|
|
126
|
+
} catch (e) {
|
|
127
|
+
positionAttempts.push({ form: "ticksString", success: false, error: e.toString() });
|
|
128
|
+
}
|
|
129
|
+
if (!positionSet && timeObj !== null) {
|
|
130
|
+
try {
|
|
131
|
+
seq.setPlayerPosition(timeObj);
|
|
132
|
+
positionAttempts.push({ form: "TimeObject", success: true });
|
|
133
|
+
positionSet = true;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
positionAttempts.push({ form: "TimeObject", success: false, error: e.toString() });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!positionSet) {
|
|
140
|
+
return JSON.stringify({
|
|
141
|
+
ok: false,
|
|
142
|
+
error: "could not move the playhead to the found edit point with any known argument form (ticks string, Time object)",
|
|
143
|
+
attempts: positionAttempts
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var playheadSeconds = null;
|
|
148
|
+
try {
|
|
149
|
+
var currentPosition = seq.getPlayerPosition();
|
|
150
|
+
if (currentPosition !== null && typeof currentPosition !== "undefined") {
|
|
151
|
+
if (typeof currentPosition.seconds === "number") {
|
|
152
|
+
playheadSeconds = currentPosition.seconds;
|
|
153
|
+
} else {
|
|
154
|
+
playheadSeconds = Number(currentPosition.ticks) / TICKS_PER_SECOND;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
playheadSeconds = null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return JSON.stringify({
|
|
162
|
+
ok: true,
|
|
163
|
+
result: {
|
|
164
|
+
sequenceName: seq.name,
|
|
165
|
+
direction: direction,
|
|
166
|
+
editPointSeconds: targetSeconds,
|
|
167
|
+
playheadSeconds: playheadSeconds,
|
|
168
|
+
attempts: positionAttempts
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
} catch (e) {
|
|
172
|
+
return JSON.stringify({ ok: false, error: e.toString() });
|
|
173
|
+
}
|
|
174
|
+
}
|