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,140 @@
|
|
|
1
|
+
// Command: replace-clip-media → ppb_replaceClipMedia
|
|
2
|
+
// Loaded lazily by ppb_dispatch (host/index.jsx) on first use.
|
|
3
|
+
//
|
|
4
|
+
// Named after leancoderkavy's premiere-pro-mcp clipboard.ts
|
|
5
|
+
// replace_clip_media, but DELIBERATELY implemented differently: that
|
|
6
|
+
// reference tool's replace_clip_media is actually just a timeline
|
|
7
|
+
// seq.overwriteClip() at a clip's start time with a different project
|
|
8
|
+
// item — functionally identical to this panel's own pre-existing
|
|
9
|
+
// replace-clip command (host/commands/replace-clip.jsx), which already
|
|
10
|
+
// covers that behavior. Porting it again under a new name would just
|
|
11
|
+
// duplicate replace-clip.
|
|
12
|
+
//
|
|
13
|
+
// Instead this command does what its name actually promises — replaces a
|
|
14
|
+
// PROJECT ITEM's underlying media FILE via the item-level API
|
|
15
|
+
// PREMIERE_API_NOTES.md documents: item.canChangeMediaPath() /
|
|
16
|
+
// item.changeMediaPath(newPath, /*overrideChecks*/true). This swaps the
|
|
17
|
+
// source file for every clip referencing that project item throughout the
|
|
18
|
+
// whole project (not just one timeline clip), which is a materially
|
|
19
|
+
// different and more powerful operation than replace-clip's per-clip
|
|
20
|
+
// swap.
|
|
21
|
+
//
|
|
22
|
+
// Destructive-ish: undo is non-functional on this build (see the `undo`
|
|
23
|
+
// command's notes) — previousMediaPath is the only path to manually
|
|
24
|
+
// revert via a second call.
|
|
25
|
+
function ppbFindItemReplaceClipMedia_walk(item, args, depth) {
|
|
26
|
+
if (depth > 32) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
var isBin = false;
|
|
30
|
+
try {
|
|
31
|
+
isBin = typeof ProjectItemType !== "undefined" && item.type === ProjectItemType.BIN;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
isBin = false;
|
|
34
|
+
}
|
|
35
|
+
var matched = false;
|
|
36
|
+
if (args.nodeId !== null) {
|
|
37
|
+
try { matched = item.nodeId === args.nodeId; } catch (e) { matched = false; }
|
|
38
|
+
} else if (args.name !== null) {
|
|
39
|
+
try { matched = item.name === args.name; } catch (e) { matched = false; }
|
|
40
|
+
}
|
|
41
|
+
if (matched) {
|
|
42
|
+
return item;
|
|
43
|
+
}
|
|
44
|
+
if (isBin && item.children) {
|
|
45
|
+
for (var i = 0; i < item.children.numItems; i++) {
|
|
46
|
+
var found = ppbFindItemReplaceClipMedia_walk(item.children[i], args, depth + 1);
|
|
47
|
+
if (found) {
|
|
48
|
+
return found;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function ppbFindItemReplaceClipMedia_resolve(args) {
|
|
56
|
+
var root = app.project.rootItem;
|
|
57
|
+
for (var i = 0; i < root.children.numItems; i++) {
|
|
58
|
+
var found = ppbFindItemReplaceClipMedia_walk(root.children[i], args, 1);
|
|
59
|
+
if (found) {
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function ppb_replaceClipMedia(argsJson) {
|
|
67
|
+
try {
|
|
68
|
+
var args;
|
|
69
|
+
try {
|
|
70
|
+
args = JSON.parse(argsJson);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return JSON.stringify({ ok: false, error: "invalid args JSON: " + e.toString() });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
var hasNodeId = typeof args.nodeId === "string" && args.nodeId.length > 0;
|
|
76
|
+
var hasName = typeof args.name === "string" && args.name.length > 0;
|
|
77
|
+
if (!hasNodeId && !hasName) {
|
|
78
|
+
return JSON.stringify({ ok: false, error: "either nodeId or name is required to identify the project item" });
|
|
79
|
+
}
|
|
80
|
+
if (typeof args.newMediaPath !== "string" || !args.newMediaPath) {
|
|
81
|
+
return JSON.stringify({ ok: false, error: "newMediaPath is required" });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!app.project) {
|
|
85
|
+
return JSON.stringify({ ok: false, error: "no project open" });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var item = ppbFindItemReplaceClipMedia_resolve({
|
|
89
|
+
nodeId: hasNodeId ? args.nodeId : null,
|
|
90
|
+
name: hasNodeId ? null : args.name
|
|
91
|
+
});
|
|
92
|
+
if (!item) {
|
|
93
|
+
return JSON.stringify({ ok: false, error: "no project item found matching the given nodeId/name" });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
var itemName = null;
|
|
97
|
+
var itemNodeId = null;
|
|
98
|
+
try { itemName = item.name; } catch (e2) { itemName = null; }
|
|
99
|
+
try { itemNodeId = item.nodeId; } catch (e3) { itemNodeId = null; }
|
|
100
|
+
|
|
101
|
+
var canChange = null;
|
|
102
|
+
try {
|
|
103
|
+
canChange = item.canChangeMediaPath();
|
|
104
|
+
} catch (e4) {
|
|
105
|
+
canChange = null;
|
|
106
|
+
}
|
|
107
|
+
if (canChange === false) {
|
|
108
|
+
return JSON.stringify({ ok: false, error: "item.canChangeMediaPath() returned false — this item's media path cannot be changed", name: itemName, nodeId: itemNodeId });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var previousMediaPath = null;
|
|
112
|
+
try { previousMediaPath = item.getMediaPath(); } catch (e5) { previousMediaPath = null; }
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
item.changeMediaPath(args.newMediaPath, true);
|
|
116
|
+
} catch (e6) {
|
|
117
|
+
return JSON.stringify({ ok: false, error: "item.changeMediaPath() failed: " + e6.toString(), name: itemName, nodeId: itemNodeId, previousMediaPath: previousMediaPath });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
var newMediaPath = null;
|
|
121
|
+
try { newMediaPath = item.getMediaPath(); } catch (e7) { newMediaPath = null; }
|
|
122
|
+
|
|
123
|
+
var verified = newMediaPath !== null && newMediaPath === args.newMediaPath;
|
|
124
|
+
|
|
125
|
+
return JSON.stringify({
|
|
126
|
+
ok: true,
|
|
127
|
+
result: {
|
|
128
|
+
name: itemName,
|
|
129
|
+
nodeId: itemNodeId,
|
|
130
|
+
canChangeMediaPath: canChange,
|
|
131
|
+
previousMediaPath: previousMediaPath,
|
|
132
|
+
requestedMediaPath: args.newMediaPath,
|
|
133
|
+
newMediaPath: newMediaPath,
|
|
134
|
+
verified: verified
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return JSON.stringify({ ok: false, error: e.toString() });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Command: replace-clip → ppb_replaceClip
|
|
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, secondsToTicksString,
|
|
5
|
+
// timeValueToSeconds, ...) are already defined there.
|
|
6
|
+
//
|
|
7
|
+
// DESTRUCTIVE: removes the addressed clip (clip.remove(false, false) —
|
|
8
|
+
// lift, not ripple, so the timeline position it occupied is preserved)
|
|
9
|
+
// and re-inserts the replacement projectItem at that same start time via
|
|
10
|
+
// seq.insertClip() — ported from leancoderkavy's premiere-pro-mcp
|
|
11
|
+
// replace_clip tool, re-addressed to this panel's
|
|
12
|
+
// trackType/trackIndex/clipIndex convention and given its own project-
|
|
13
|
+
// item addressing (nodeId/name) for the replacement, matching
|
|
14
|
+
// get-item-metadata's convention. Undo is NON-FUNCTIONAL on this build —
|
|
15
|
+
// if the replacement projectItem is wrong, the ORIGINAL clip's own
|
|
16
|
+
// details (returned as `before`) are the only path to manually reinsert
|
|
17
|
+
// it.
|
|
18
|
+
//
|
|
19
|
+
// MUTATION RULE: verified by re-finding a clip at the same start time on
|
|
20
|
+
// the same track afterward and confirming its projectItem's name/media
|
|
21
|
+
// path changed to the replacement's.
|
|
22
|
+
|
|
23
|
+
function ppbFindItemReplaceClip_walk(item, args, depth) {
|
|
24
|
+
if (depth > 32) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
var isBin = false;
|
|
28
|
+
try {
|
|
29
|
+
isBin = typeof ProjectItemType !== "undefined" && item.type === ProjectItemType.BIN;
|
|
30
|
+
} catch (e) {
|
|
31
|
+
isBin = false;
|
|
32
|
+
}
|
|
33
|
+
var matched = false;
|
|
34
|
+
if (args.nodeId !== null) {
|
|
35
|
+
try { matched = item.nodeId === args.nodeId; } catch (e) { matched = false; }
|
|
36
|
+
} else if (args.name !== null) {
|
|
37
|
+
try { matched = item.name === args.name; } catch (e) { matched = false; }
|
|
38
|
+
}
|
|
39
|
+
if (matched) {
|
|
40
|
+
return item;
|
|
41
|
+
}
|
|
42
|
+
if (isBin && item.children) {
|
|
43
|
+
for (var i = 0; i < item.children.numItems; i++) {
|
|
44
|
+
var found = ppbFindItemReplaceClip_walk(item.children[i], args, depth + 1);
|
|
45
|
+
if (found) {
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ppbFindItemReplaceClip_resolve(args) {
|
|
54
|
+
var root = app.project.rootItem;
|
|
55
|
+
for (var i = 0; i < root.children.numItems; i++) {
|
|
56
|
+
var found = ppbFindItemReplaceClip_walk(root.children[i], args, 1);
|
|
57
|
+
if (found) {
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ppb_replaceClip(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" || typeof args.clipIndex !== "number") {
|
|
77
|
+
return JSON.stringify({ ok: false, error: "trackIndex and clipIndex are required" });
|
|
78
|
+
}
|
|
79
|
+
var hasNodeId = typeof args.replacementNodeId === "string" && args.replacementNodeId.length > 0;
|
|
80
|
+
var hasName = typeof args.replacementName === "string" && args.replacementName.length > 0;
|
|
81
|
+
if (!hasNodeId && !hasName) {
|
|
82
|
+
return JSON.stringify({ ok: false, error: "either replacementNodeId or replacementName is required" });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!app.project) {
|
|
86
|
+
return JSON.stringify({ ok: false, error: "no project open" });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
var seq = null;
|
|
90
|
+
if (args.sequenceName && typeof args.sequenceName === "string") {
|
|
91
|
+
seq = findSequenceByName(args.sequenceName);
|
|
92
|
+
if (!seq) {
|
|
93
|
+
return JSON.stringify({ ok: false, error: "no sequence named \"" + args.sequenceName + "\" is open" });
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
seq = app.project.activeSequence;
|
|
97
|
+
if (!seq) {
|
|
98
|
+
return JSON.stringify({ ok: false, error: "no active sequence, and no sequenceName given" });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var resolved = resolveTimelineClip(seq, args.trackType, args.trackIndex, args.clipIndex);
|
|
103
|
+
if (resolved.error) {
|
|
104
|
+
return JSON.stringify({ ok: false, error: resolved.error });
|
|
105
|
+
}
|
|
106
|
+
var clip = resolved.clip;
|
|
107
|
+
|
|
108
|
+
var replacementItem = ppbFindItemReplaceClip_resolve({
|
|
109
|
+
nodeId: hasNodeId ? args.replacementNodeId : null,
|
|
110
|
+
name: hasNodeId ? null : args.replacementName
|
|
111
|
+
});
|
|
112
|
+
if (!replacementItem) {
|
|
113
|
+
return JSON.stringify({ ok: false, error: "no replacement project item found matching the given replacementNodeId/replacementName" });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var before = serializeTrackItem(clip, args.trackIndex, args.clipIndex);
|
|
117
|
+
if (before.startSeconds === null) {
|
|
118
|
+
return JSON.stringify({ ok: false, error: "could not read the clip's own start time — refusing to remove it without a safe reinsertion point", removedClip: null });
|
|
119
|
+
}
|
|
120
|
+
var startSeconds = before.startSeconds;
|
|
121
|
+
|
|
122
|
+
var trackCollection = args.trackType === "video" ? seq.videoTracks : seq.audioTracks;
|
|
123
|
+
var track = trackCollection[args.trackIndex];
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
clip.remove(false, false);
|
|
127
|
+
} catch (e2) {
|
|
128
|
+
return JSON.stringify({ ok: false, error: "clip.remove() failed while removing the original clip: " + e2.toString(), previousValue: before });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var vIdx = args.trackType === "video" ? args.trackIndex : -1;
|
|
132
|
+
var aIdx = args.trackType === "audio" ? args.trackIndex : -1;
|
|
133
|
+
var startTicks = secondsToTicksString(startSeconds);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
seq.insertClip(replacementItem, startTicks, vIdx, aIdx);
|
|
137
|
+
} catch (e3) {
|
|
138
|
+
return JSON.stringify({
|
|
139
|
+
ok: false,
|
|
140
|
+
error: "the original clip was already removed but seq.insertClip() of the replacement failed: " + e3.toString() + " — the track now has a gap; previousValue below is the only path to manually reinsert the original",
|
|
141
|
+
previousValue: before
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
var replacementNodeId = null;
|
|
146
|
+
try { replacementNodeId = replacementItem.nodeId; } catch (e4) { replacementNodeId = null; }
|
|
147
|
+
|
|
148
|
+
var newClip = null;
|
|
149
|
+
var toleranceSeconds = 0.1;
|
|
150
|
+
try {
|
|
151
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
152
|
+
var candidate = track.clips[c];
|
|
153
|
+
var candNodeId = null;
|
|
154
|
+
try { candNodeId = candidate.projectItem.nodeId; } catch (e5) { candNodeId = null; }
|
|
155
|
+
if (candNodeId !== replacementNodeId) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
var candStart = null;
|
|
159
|
+
try { candStart = timeValueToSeconds(candidate.start); } catch (e6) { candStart = null; }
|
|
160
|
+
if (candStart !== null && Math.abs(candStart - startSeconds) <= toleranceSeconds) {
|
|
161
|
+
newClip = serializeTrackItem(candidate, args.trackIndex, c);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (e7) {
|
|
166
|
+
newClip = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
var verified = newClip !== null && newClip.mediaPath !== null &&
|
|
170
|
+
(function () { try { return newClip.mediaPath === replacementItem.getMediaPath(); } catch (e) { return false; } })();
|
|
171
|
+
|
|
172
|
+
return JSON.stringify({
|
|
173
|
+
ok: true,
|
|
174
|
+
result: {
|
|
175
|
+
sequenceName: seq.name,
|
|
176
|
+
trackType: args.trackType,
|
|
177
|
+
trackIndex: args.trackIndex,
|
|
178
|
+
clipIndex: args.clipIndex,
|
|
179
|
+
previousValue: before,
|
|
180
|
+
newValue: newClip,
|
|
181
|
+
replacementItem: { name: (function () { try { return replacementItem.name; } catch (e) { return null; } })(), nodeId: replacementNodeId },
|
|
182
|
+
verified: verified
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
} catch (e) {
|
|
186
|
+
return JSON.stringify({ ok: false, error: e.toString() });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Command: reverse-clip → ppb_reverseClip
|
|
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, TICKS_PER_SECOND, ...) are already defined there.
|
|
6
|
+
//
|
|
7
|
+
// Reverses (or un-reverses) a clip's playback direction. REWRITTEN
|
|
8
|
+
// 2026-07-17 after the probe session (see BUILD_FINDINGS.md corrections):
|
|
9
|
+
// the working route on this build is the live-calibrated 5-arg
|
|
10
|
+
// qeClip.setSpeed(speedMultiplier, ticksString, reverse, false, false)
|
|
11
|
+
// whose THIRD argument is the reverse flag (verified via
|
|
12
|
+
// isSpeedReversed()). Negative speeds do NOT reverse — they clamp to a
|
|
13
|
+
// degenerate state — so the current speed magnitude is preserved and
|
|
14
|
+
// only the flag flips. setReverse() is still tried first in case a
|
|
15
|
+
// future build grows it (it did not exist on 26.3.0).
|
|
16
|
+
//
|
|
17
|
+
// getSpeed()/setSpeed speeds are MULTIPLIERS on this build (1 = 100%).
|
|
18
|
+
// Verified via the standard DOM's clip.isSpeedReversed() read-back —
|
|
19
|
+
// never via either QE call's own return value.
|
|
20
|
+
|
|
21
|
+
function ppb_reverseClip(argsJson) {
|
|
22
|
+
try {
|
|
23
|
+
var args;
|
|
24
|
+
try {
|
|
25
|
+
args = JSON.parse(argsJson);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return JSON.stringify({ ok: false, error: "invalid args JSON: " + e.toString() });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (args.trackType !== "video" && args.trackType !== "audio") {
|
|
31
|
+
return JSON.stringify({ ok: false, error: "trackType must be \"video\" or \"audio\"" });
|
|
32
|
+
}
|
|
33
|
+
if (typeof args.trackIndex !== "number" || args.trackIndex < 0 || Math.floor(args.trackIndex) !== args.trackIndex) {
|
|
34
|
+
return JSON.stringify({ ok: false, error: "trackIndex must be a non-negative integer" });
|
|
35
|
+
}
|
|
36
|
+
if (typeof args.clipIndex !== "number" || args.clipIndex < 0 || Math.floor(args.clipIndex) !== args.clipIndex) {
|
|
37
|
+
return JSON.stringify({ ok: false, error: "clipIndex must be a non-negative integer" });
|
|
38
|
+
}
|
|
39
|
+
var reverse = args.reverse !== false; // default true, matching the reference tool
|
|
40
|
+
|
|
41
|
+
if (!app.project) {
|
|
42
|
+
return JSON.stringify({ ok: false, error: "no project open" });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var seq = null;
|
|
46
|
+
if (args.sequenceName && typeof args.sequenceName === "string") {
|
|
47
|
+
seq = findSequenceByName(args.sequenceName);
|
|
48
|
+
if (!seq) {
|
|
49
|
+
return JSON.stringify({ ok: false, error: "no sequence named \"" + args.sequenceName + "\" is open" });
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
seq = app.project.activeSequence;
|
|
53
|
+
if (!seq) {
|
|
54
|
+
return JSON.stringify({ ok: false, error: "no active sequence, and no sequenceName given" });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
var resolved = resolveTimelineClip(seq, args.trackType, args.trackIndex, args.clipIndex);
|
|
59
|
+
if (resolved.error) {
|
|
60
|
+
return JSON.stringify({ ok: false, error: resolved.error });
|
|
61
|
+
}
|
|
62
|
+
var clip = resolved.clip;
|
|
63
|
+
var clipName = null;
|
|
64
|
+
try { clipName = clip.name; } catch (e) { clipName = null; }
|
|
65
|
+
|
|
66
|
+
var previousReversed = null;
|
|
67
|
+
try { previousReversed = clip.isSpeedReversed() == 1; } catch (e) { previousReversed = null; }
|
|
68
|
+
var previousSpeed = null;
|
|
69
|
+
try { previousSpeed = clip.getSpeed(); } catch (e) { previousSpeed = null; }
|
|
70
|
+
|
|
71
|
+
if (previousReversed === reverse) {
|
|
72
|
+
return JSON.stringify({
|
|
73
|
+
ok: true,
|
|
74
|
+
result: {
|
|
75
|
+
sequenceName: seq.name,
|
|
76
|
+
trackType: args.trackType,
|
|
77
|
+
trackIndex: args.trackIndex,
|
|
78
|
+
clipIndex: args.clipIndex,
|
|
79
|
+
clipName: clipName,
|
|
80
|
+
requestedReversed: reverse,
|
|
81
|
+
previousReversed: previousReversed,
|
|
82
|
+
newReversed: previousReversed,
|
|
83
|
+
alreadyInRequestedState: true,
|
|
84
|
+
verified: true
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// setSpeed's ticks argument must be a plausible value — the clip's
|
|
90
|
+
// own current timeline duration (garbage there collapses the clip to
|
|
91
|
+
// one frame, per the calibration notes).
|
|
92
|
+
var durTicks = null;
|
|
93
|
+
try {
|
|
94
|
+
durTicks = String(Math.round((clip.end.seconds - clip.start.seconds) * TICKS_PER_SECOND));
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return JSON.stringify({ ok: false, error: "could not read the clip's duration for setSpeed's ticks argument: " + e.toString() });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
ensureQEEnabled();
|
|
101
|
+
activateSequenceForQE(seq);
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return JSON.stringify({ ok: false, error: "app.enableQE()/sequence activation failed: " + e.toString() });
|
|
104
|
+
}
|
|
105
|
+
if (typeof qe === "undefined" || !qe.project) {
|
|
106
|
+
return JSON.stringify({ ok: false, error: "QE DOM not available after enableQE()" });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
var qeSeq;
|
|
110
|
+
try {
|
|
111
|
+
qeSeq = qe.project.getActiveSequence();
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return JSON.stringify({ ok: false, error: "qe.project.getActiveSequence() failed: " + e.toString() });
|
|
114
|
+
}
|
|
115
|
+
var qeResolved = resolveQeClip(qeSeq, args.trackType, args.trackIndex, args.clipIndex);
|
|
116
|
+
if (qeResolved.error) {
|
|
117
|
+
return JSON.stringify({ ok: false, error: qeResolved.error });
|
|
118
|
+
}
|
|
119
|
+
var qeClip = qeResolved.qeClip;
|
|
120
|
+
|
|
121
|
+
var attempts = [];
|
|
122
|
+
var succeeded = false;
|
|
123
|
+
var formUsed = null;
|
|
124
|
+
|
|
125
|
+
// setReverse() did not exist on 26.3.0 — kept as a cheap first probe
|
|
126
|
+
// in case a future build grows it.
|
|
127
|
+
if (typeof qeClip.setReverse === "function") {
|
|
128
|
+
try {
|
|
129
|
+
qeClip.setReverse(reverse);
|
|
130
|
+
attempts.push({ form: "setReverse(bool)", success: true });
|
|
131
|
+
succeeded = true;
|
|
132
|
+
formUsed = "setReverse(bool)";
|
|
133
|
+
} catch (e) {
|
|
134
|
+
attempts.push({ form: "setReverse(bool)", success: false, error: e.toString() });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!succeeded && typeof qeClip.setSpeed === "function") {
|
|
139
|
+
// preserve the current speed magnitude; getSpeed() is a multiplier
|
|
140
|
+
var magnitude = (previousSpeed !== null && previousSpeed !== 0) ? Math.abs(previousSpeed) : 1;
|
|
141
|
+
try {
|
|
142
|
+
qeClip.setSpeed(magnitude, durTicks, reverse, false, false);
|
|
143
|
+
attempts.push({ form: "setSpeed(multiplier, ticksString, reverse, false, false)", success: true });
|
|
144
|
+
succeeded = true;
|
|
145
|
+
formUsed = "setSpeed(multiplier, ticksString, reverse, false, false)";
|
|
146
|
+
} catch (e) {
|
|
147
|
+
attempts.push({ form: "setSpeed(multiplier, ticksString, reverse, false, false)", success: false, error: e.toString() });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!succeeded) {
|
|
152
|
+
return JSON.stringify({
|
|
153
|
+
ok: false,
|
|
154
|
+
error: "could not reverse the clip with setReverse() or the calibrated setSpeed() form",
|
|
155
|
+
attempts: attempts
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
var newReversed = null;
|
|
160
|
+
try { newReversed = clip.isSpeedReversed() == 1; } catch (e) { newReversed = null; }
|
|
161
|
+
var newSpeed = null;
|
|
162
|
+
try { newSpeed = clip.getSpeed(); } catch (e) { newSpeed = null; }
|
|
163
|
+
|
|
164
|
+
var result = {
|
|
165
|
+
sequenceName: seq.name,
|
|
166
|
+
trackType: args.trackType,
|
|
167
|
+
trackIndex: args.trackIndex,
|
|
168
|
+
clipIndex: args.clipIndex,
|
|
169
|
+
clipName: clipName,
|
|
170
|
+
requestedReversed: reverse,
|
|
171
|
+
formUsed: formUsed,
|
|
172
|
+
attempts: attempts,
|
|
173
|
+
previousReversed: previousReversed,
|
|
174
|
+
newReversed: newReversed,
|
|
175
|
+
previousSpeed: previousSpeed,
|
|
176
|
+
newSpeed: newSpeed,
|
|
177
|
+
verified: newReversed === reverse
|
|
178
|
+
};
|
|
179
|
+
if (newReversed === null) {
|
|
180
|
+
result.note = "clip.isSpeedReversed() was not readable on this build — the QE call did not throw, but the result is unverified.";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return JSON.stringify({ ok: true, result: result });
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return JSON.stringify({ ok: false, error: e.toString() });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Command: ripple-delete-clip → ppb_rippleDeleteClip
|
|
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, ...) are already defined there.
|
|
6
|
+
//
|
|
7
|
+
// **Destructive.** Ripple-deletes ONE clip (removes it and closes the
|
|
8
|
+
// gap, shifting later clips earlier) — the single-clip counterpart of
|
|
9
|
+
// remove-track-intervals' batch interval removal. Ported from
|
|
10
|
+
// leancoderkavy's premiere-pro-mcp `ripple_delete` tool (advanced.ts),
|
|
11
|
+
// which calls `qeClip.rippleDelete()` unconditionally and trusts it.
|
|
12
|
+
// Per this bridge's MUTATION RULE, success is instead judged the same way
|
|
13
|
+
// remove-track-intervals' findAndRemoveInRange does: an actual numItems
|
|
14
|
+
// drop on the QE track, with a `remove(bool, bool)` attempts fallback if
|
|
15
|
+
// rippleDelete() didn't move the needle (both calls are unreliable in
|
|
16
|
+
// practice on this build — neither's own return value nor the absence of
|
|
17
|
+
// a thrown exception is trustworthy). Undo is non-functional on this
|
|
18
|
+
// build (see README), so there is no way back from this command short of
|
|
19
|
+
// re-inserting the clip's source media by hand.
|
|
20
|
+
|
|
21
|
+
function ppb_rippleDeleteClip(argsJson) {
|
|
22
|
+
try {
|
|
23
|
+
var args;
|
|
24
|
+
try {
|
|
25
|
+
args = JSON.parse(argsJson);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return JSON.stringify({ ok: false, error: "invalid args JSON: " + e.toString() });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (args.trackType !== "video" && args.trackType !== "audio") {
|
|
31
|
+
return JSON.stringify({ ok: false, error: "trackType must be \"video\" or \"audio\"" });
|
|
32
|
+
}
|
|
33
|
+
if (typeof args.trackIndex !== "number" || args.trackIndex < 0 || Math.floor(args.trackIndex) !== args.trackIndex) {
|
|
34
|
+
return JSON.stringify({ ok: false, error: "trackIndex must be a non-negative integer" });
|
|
35
|
+
}
|
|
36
|
+
if (typeof args.clipIndex !== "number" || args.clipIndex < 0 || Math.floor(args.clipIndex) !== args.clipIndex) {
|
|
37
|
+
return JSON.stringify({ ok: false, error: "clipIndex must be a non-negative integer" });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!app.project) {
|
|
41
|
+
return JSON.stringify({ ok: false, error: "no project open" });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var seq = null;
|
|
45
|
+
if (args.sequenceName && typeof args.sequenceName === "string") {
|
|
46
|
+
seq = findSequenceByName(args.sequenceName);
|
|
47
|
+
if (!seq) {
|
|
48
|
+
return JSON.stringify({ ok: false, error: "no sequence named \"" + args.sequenceName + "\" is open" });
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
seq = app.project.activeSequence;
|
|
52
|
+
if (!seq) {
|
|
53
|
+
return JSON.stringify({ ok: false, error: "no active sequence, and no sequenceName given" });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var resolved = resolveTimelineClip(seq, args.trackType, args.trackIndex, args.clipIndex);
|
|
58
|
+
if (resolved.error) {
|
|
59
|
+
return JSON.stringify({ ok: false, error: resolved.error });
|
|
60
|
+
}
|
|
61
|
+
var before = serializeTrackItem(resolved.clip, args.trackIndex, args.clipIndex);
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
ensureQEEnabled();
|
|
65
|
+
activateSequenceForQE(seq);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return JSON.stringify({ ok: false, error: "app.enableQE()/sequence activation failed: " + e.toString() });
|
|
68
|
+
}
|
|
69
|
+
if (typeof qe === "undefined" || !qe.project) {
|
|
70
|
+
return JSON.stringify({ ok: false, error: "QE DOM not available after enableQE()" });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var qeSeq;
|
|
74
|
+
try {
|
|
75
|
+
qeSeq = qe.project.getActiveSequence();
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return JSON.stringify({ ok: false, error: "qe.project.getActiveSequence() failed: " + e.toString() });
|
|
78
|
+
}
|
|
79
|
+
var qeResolved = resolveQeClip(qeSeq, args.trackType, args.trackIndex, args.clipIndex);
|
|
80
|
+
if (qeResolved.error) {
|
|
81
|
+
return JSON.stringify({ ok: false, error: qeResolved.error });
|
|
82
|
+
}
|
|
83
|
+
var qeClip = qeResolved.qeClip;
|
|
84
|
+
var qeTrack = qeResolved.qeTrack;
|
|
85
|
+
|
|
86
|
+
var previousItemCount = qeTrack.numItems;
|
|
87
|
+
var removed = false;
|
|
88
|
+
var method = null;
|
|
89
|
+
|
|
90
|
+
if (typeof qeClip.rippleDelete === "function") {
|
|
91
|
+
try {
|
|
92
|
+
qeClip.rippleDelete();
|
|
93
|
+
} catch (e) {
|
|
94
|
+
// fall through to the numItems check regardless — this call's
|
|
95
|
+
// own return value/thrown-ness is not trustworthy on this build
|
|
96
|
+
}
|
|
97
|
+
removed = qeTrack.numItems < previousItemCount;
|
|
98
|
+
if (removed) {
|
|
99
|
+
method = "rippleDelete";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!removed && typeof qeClip.remove === "function") {
|
|
104
|
+
// remove()'s two boolean params are undocumented — try plausible
|
|
105
|
+
// combinations, same fallback order as remove-track-intervals.
|
|
106
|
+
var attempts = [[true, false], [true, true], [false, true], [false, false]];
|
|
107
|
+
for (var a = 0; a < attempts.length && !removed; a++) {
|
|
108
|
+
var countBeforeAttempt = qeTrack.numItems;
|
|
109
|
+
try {
|
|
110
|
+
qeClip.remove(attempts[a][0], attempts[a][1]);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
// fall through to the numItems check below regardless
|
|
113
|
+
}
|
|
114
|
+
if (qeTrack.numItems < countBeforeAttempt) {
|
|
115
|
+
removed = true;
|
|
116
|
+
method = "remove(" + attempts[a][0] + ", " + attempts[a][1] + ")";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
var newItemCount = qeTrack.numItems;
|
|
122
|
+
|
|
123
|
+
if (!removed) {
|
|
124
|
+
return JSON.stringify({
|
|
125
|
+
ok: false,
|
|
126
|
+
error: "clip could not be ripple-deleted — neither rippleDelete() nor remove() with any boolean combination reduced the QE track's item count",
|
|
127
|
+
result: {
|
|
128
|
+
sequenceName: seq.name,
|
|
129
|
+
trackType: args.trackType,
|
|
130
|
+
trackIndex: args.trackIndex,
|
|
131
|
+
clipIndex: args.clipIndex,
|
|
132
|
+
clip: before,
|
|
133
|
+
previousItemCount: previousItemCount,
|
|
134
|
+
newItemCount: newItemCount
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return JSON.stringify({
|
|
140
|
+
ok: true,
|
|
141
|
+
result: {
|
|
142
|
+
sequenceName: seq.name,
|
|
143
|
+
trackType: args.trackType,
|
|
144
|
+
trackIndex: args.trackIndex,
|
|
145
|
+
clipIndex: args.clipIndex,
|
|
146
|
+
removedClip: before,
|
|
147
|
+
method: method,
|
|
148
|
+
previousItemCount: previousItemCount,
|
|
149
|
+
newItemCount: newItemCount,
|
|
150
|
+
verified: newItemCount < previousItemCount
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
} catch (e) {
|
|
154
|
+
return JSON.stringify({ ok: false, error: e.toString() });
|
|
155
|
+
}
|
|
156
|
+
}
|