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.
Files changed (247) hide show
  1. premiere_cli/__init__.py +11 -0
  2. premiere_cli/cli.py +3664 -0
  3. premiere_cli/log.py +66 -0
  4. premiere_cli/panel/.debug +8 -0
  5. premiere_cli/panel/CSXS/manifest.xml +52 -0
  6. premiere_cli/panel/css/style.css +53 -0
  7. premiere_cli/panel/host/commands/add-adjustment-layer.jsx +190 -0
  8. premiere_cli/panel/host/commands/add-audio-keyframes.jsx +176 -0
  9. premiere_cli/panel/host/commands/add-custom-metadata-field.jsx +63 -0
  10. premiere_cli/panel/host/commands/add-keyframe.jsx +181 -0
  11. premiere_cli/panel/host/commands/add-marker-to-project-item.jsx +194 -0
  12. premiere_cli/panel/host/commands/add-marker.jsx +129 -0
  13. premiere_cli/panel/host/commands/add-text-overlay.jsx +101 -0
  14. premiere_cli/panel/host/commands/add-to-render-queue.jsx +120 -0
  15. premiere_cli/panel/host/commands/add-to-timeline.jsx +300 -0
  16. premiere_cli/panel/host/commands/add-track.jsx +188 -0
  17. premiere_cli/panel/host/commands/add-transition.jsx +134 -0
  18. premiere_cli/panel/host/commands/adjust-audio-levels.jsx +132 -0
  19. premiere_cli/panel/host/commands/apply-audio-effect.jsx +118 -0
  20. premiere_cli/panel/host/commands/apply-effect.jsx +122 -0
  21. premiere_cli/panel/host/commands/apply-lut.jsx +107 -0
  22. premiere_cli/panel/host/commands/attach-custom-property.jsx +66 -0
  23. premiere_cli/panel/host/commands/auto-reframe-sequence.jsx +124 -0
  24. premiere_cli/panel/host/commands/batch-add-transitions.jsx +158 -0
  25. premiere_cli/panel/host/commands/batch-apply-effect.jsx +182 -0
  26. premiere_cli/panel/host/commands/batch-rename-clips.jsx +170 -0
  27. premiere_cli/panel/host/commands/batch-set-clips-enabled.jsx +145 -0
  28. premiere_cli/panel/host/commands/clear-item-in-out.jsx +117 -0
  29. premiere_cli/panel/host/commands/clear-sequence-in-out.jsx +100 -0
  30. premiere_cli/panel/host/commands/close-all-source-clips.jsx +32 -0
  31. premiere_cli/panel/host/commands/close-sequence.jsx +80 -0
  32. premiere_cli/panel/host/commands/close-source-monitor.jsx +32 -0
  33. premiere_cli/panel/host/commands/color-correct.jsx +164 -0
  34. premiere_cli/panel/host/commands/copy-effect-values.jsx +183 -0
  35. premiere_cli/panel/host/commands/copy-effects-between-clips.jsx +234 -0
  36. premiere_cli/panel/host/commands/create-bars-and-tone.jsx +152 -0
  37. premiere_cli/panel/host/commands/create-bin.jsx +76 -0
  38. premiere_cli/panel/host/commands/create-caption-track.jsx +159 -0
  39. premiere_cli/panel/host/commands/create-sequence-from-clips.jsx +170 -0
  40. premiere_cli/panel/host/commands/create-sequence-from-preset.jsx +90 -0
  41. premiere_cli/panel/host/commands/create-sequence.jsx +166 -0
  42. premiere_cli/panel/host/commands/create-smart-bin.jsx +80 -0
  43. premiere_cli/panel/host/commands/create-subclip.jsx +248 -0
  44. premiere_cli/panel/host/commands/create-subsequence.jsx +136 -0
  45. premiere_cli/panel/host/commands/debug-qe-inspect.jsx +248 -0
  46. premiere_cli/panel/host/commands/debug-qe-try-mutate.jsx +305 -0
  47. premiere_cli/panel/host/commands/delete-marker.jsx +178 -0
  48. premiere_cli/panel/host/commands/deselect-all-clips.jsx +98 -0
  49. premiere_cli/panel/host/commands/detach-proxy.jsx +116 -0
  50. premiere_cli/panel/host/commands/duplicate-clip.jsx +140 -0
  51. premiere_cli/panel/host/commands/duplicate-sequence.jsx +150 -0
  52. premiere_cli/panel/host/commands/encode-file.jsx +86 -0
  53. premiere_cli/panel/host/commands/encode-project-item.jsx +145 -0
  54. premiere_cli/panel/host/commands/export-aaf.jsx +77 -0
  55. premiere_cli/panel/host/commands/export-fcp-xml.jsx +65 -0
  56. premiere_cli/panel/host/commands/export-frame.jsx +381 -0
  57. premiere_cli/panel/host/commands/export-omf.jsx +95 -0
  58. premiere_cli/panel/host/commands/export-sequence-as-project.jsx +65 -0
  59. premiere_cli/panel/host/commands/export-sequence.jsx +100 -0
  60. premiere_cli/panel/host/commands/extract-audio-track.jsx +181 -0
  61. premiere_cli/panel/host/commands/extract-selection.jsx +113 -0
  62. premiere_cli/panel/host/commands/find-items-by-media-path.jsx +112 -0
  63. premiere_cli/panel/host/commands/freeze-frame.jsx +215 -0
  64. premiere_cli/panel/host/commands/get-active-sequence.jsx +103 -0
  65. premiere_cli/panel/host/commands/get-all-project-paths.jsx +72 -0
  66. premiere_cli/panel/host/commands/get-bin-contents.jsx +176 -0
  67. premiere_cli/panel/host/commands/get-clip-adjustment-layer.jsx +83 -0
  68. premiere_cli/panel/host/commands/get-clip-at-playhead.jsx +99 -0
  69. premiere_cli/panel/host/commands/get-clip-at-position.jsx +120 -0
  70. premiere_cli/panel/host/commands/get-clip-links.jsx +135 -0
  71. premiere_cli/panel/host/commands/get-clip-markers.jsx +144 -0
  72. premiere_cli/panel/host/commands/get-clip-speed.jsx +69 -0
  73. premiere_cli/panel/host/commands/get-color-label.jsx +89 -0
  74. premiere_cli/panel/host/commands/get-color-space.jsx +99 -0
  75. premiere_cli/panel/host/commands/get-duplicate-media.jsx +78 -0
  76. premiere_cli/panel/host/commands/get-effect-properties.jsx +129 -0
  77. premiere_cli/panel/host/commands/get-encoder-presets.jsx +131 -0
  78. premiere_cli/panel/host/commands/get-export-file-extension.jsx +56 -0
  79. premiere_cli/panel/host/commands/get-footage-interpretation.jsx +113 -0
  80. premiere_cli/panel/host/commands/get-full-clip-info.jsx +162 -0
  81. premiere_cli/panel/host/commands/get-full-project-overview.jsx +193 -0
  82. premiere_cli/panel/host/commands/get-full-sequence-info.jsx +184 -0
  83. premiere_cli/panel/host/commands/get-graphics-white-luminance.jsx +31 -0
  84. premiere_cli/panel/host/commands/get-insertion-bin.jsx +35 -0
  85. premiere_cli/panel/host/commands/get-item-metadata.jsx +94 -0
  86. premiere_cli/panel/host/commands/get-keyframes.jsx +158 -0
  87. premiere_cli/panel/host/commands/get-mogrt-component.jsx +111 -0
  88. premiere_cli/panel/host/commands/get-next-edit-point.jsx +131 -0
  89. premiere_cli/panel/host/commands/get-offline-media.jsx +67 -0
  90. premiere_cli/panel/host/commands/get-open-projects.jsx +50 -0
  91. premiere_cli/panel/host/commands/get-premiere-state.jsx +164 -0
  92. premiere_cli/panel/host/commands/get-project-info.jsx +63 -0
  93. premiere_cli/panel/host/commands/get-project-item-info.jsx +247 -0
  94. premiere_cli/panel/host/commands/get-project-panel-metadata.jsx +30 -0
  95. premiere_cli/panel/host/commands/get-qe-clip-info.jsx +198 -0
  96. premiere_cli/panel/host/commands/get-render-queue-status.jsx +35 -0
  97. premiere_cli/panel/host/commands/get-sequence-count.jsx +24 -0
  98. premiere_cli/panel/host/commands/get-sequence-in-out.jsx +76 -0
  99. premiere_cli/panel/host/commands/get-sequence-markers-by-type.jsx +89 -0
  100. premiere_cli/panel/host/commands/get-source-monitor-info.jsx +34 -0
  101. premiere_cli/panel/host/commands/get-source-monitor-position.jsx +30 -0
  102. premiere_cli/panel/host/commands/get-target-tracks.jsx +76 -0
  103. premiere_cli/panel/host/commands/get-timeline-gaps.jsx +124 -0
  104. premiere_cli/panel/host/commands/get-timeline-summary.jsx +154 -0
  105. premiere_cli/panel/host/commands/get-total-clip-count.jsx +67 -0
  106. premiere_cli/panel/host/commands/get-track-info.jsx +117 -0
  107. premiere_cli/panel/host/commands/get-unused-media.jsx +99 -0
  108. premiere_cli/panel/host/commands/get-used-media-report.jsx +125 -0
  109. premiere_cli/panel/host/commands/get-value-at-time.jsx +135 -0
  110. premiere_cli/panel/host/commands/get-version-info.jsx +33 -0
  111. premiere_cli/panel/host/commands/get-work-area.jsx +52 -0
  112. premiere_cli/panel/host/commands/get-workspaces.jsx +29 -0
  113. premiere_cli/panel/host/commands/get-xmp-metadata.jsx +107 -0
  114. premiere_cli/panel/host/commands/import-ae-comps.jsx +129 -0
  115. premiere_cli/panel/host/commands/import-fcp-xml.jsx +103 -0
  116. premiere_cli/panel/host/commands/import-folder.jsx +193 -0
  117. premiere_cli/panel/host/commands/import-image-sequence.jsx +171 -0
  118. premiere_cli/panel/host/commands/import-media.jsx +217 -0
  119. premiere_cli/panel/host/commands/import-mogrt-from-library.jsx +105 -0
  120. premiere_cli/panel/host/commands/import-mogrt.jsx +111 -0
  121. premiere_cli/panel/host/commands/import-sequences-from-project.jsx +80 -0
  122. premiere_cli/panel/host/commands/insert-from-source.jsx +162 -0
  123. premiere_cli/panel/host/commands/inspect-dom-object.jsx +162 -0
  124. premiere_cli/panel/host/commands/invert-selection.jsx +114 -0
  125. premiere_cli/panel/host/commands/is-work-area-enabled.jsx +88 -0
  126. premiere_cli/panel/host/commands/lift-selection.jsx +106 -0
  127. premiere_cli/panel/host/commands/link-selection.jsx +111 -0
  128. premiere_cli/panel/host/commands/list-available-audio-effects.jsx +33 -0
  129. premiere_cli/panel/host/commands/list-available-audio-transitions.jsx +35 -0
  130. premiere_cli/panel/host/commands/list-available-effects.jsx +33 -0
  131. premiere_cli/panel/host/commands/list-available-transitions.jsx +74 -0
  132. premiere_cli/panel/host/commands/list-markers.jsx +69 -0
  133. premiere_cli/panel/host/commands/list-project-items.jsx +105 -0
  134. premiere_cli/panel/host/commands/lock-track.jsx +114 -0
  135. premiere_cli/panel/host/commands/manage-proxies.jsx +176 -0
  136. premiere_cli/panel/host/commands/match-frame.jsx +141 -0
  137. premiere_cli/panel/host/commands/move-clip-to-track.jsx +220 -0
  138. premiere_cli/panel/host/commands/move-clip.jsx +97 -0
  139. premiere_cli/panel/host/commands/move-items-to-bin.jsx +194 -0
  140. premiere_cli/panel/host/commands/move-playhead-to-edit.jsx +174 -0
  141. premiere_cli/panel/host/commands/move-playhead.jsx +137 -0
  142. premiere_cli/panel/host/commands/nest-clips.jsx +179 -0
  143. premiere_cli/panel/host/commands/open-in-source.jsx +117 -0
  144. premiere_cli/panel/host/commands/open-project.jsx +61 -0
  145. premiere_cli/panel/host/commands/overwrite-clip-at.jsx +350 -0
  146. premiere_cli/panel/host/commands/overwrite-from-source.jsx +150 -0
  147. premiere_cli/panel/host/commands/play-source-monitor.jsx +36 -0
  148. premiere_cli/panel/host/commands/play-timeline.jsx +24 -0
  149. premiere_cli/panel/host/commands/razor-all-tracks.jsx +171 -0
  150. premiere_cli/panel/host/commands/redo.jsx +69 -0
  151. premiere_cli/panel/host/commands/refresh-media.jsx +101 -0
  152. premiere_cli/panel/host/commands/relink-media.jsx +132 -0
  153. premiere_cli/panel/host/commands/remove-all-effects.jsx +114 -0
  154. premiere_cli/panel/host/commands/remove-effect-by-name.jsx +143 -0
  155. premiere_cli/panel/host/commands/remove-effect.jsx +122 -0
  156. premiere_cli/panel/host/commands/remove-from-timeline.jsx +93 -0
  157. premiere_cli/panel/host/commands/remove-keyframe-range.jsx +173 -0
  158. premiere_cli/panel/host/commands/remove-keyframe.jsx +192 -0
  159. premiere_cli/panel/host/commands/remove-selected-clips.jsx +126 -0
  160. premiere_cli/panel/host/commands/remove-track-intervals.jsx +254 -0
  161. premiere_cli/panel/host/commands/remove-transition.jsx +164 -0
  162. premiere_cli/panel/host/commands/rename-bin.jsx +88 -0
  163. premiere_cli/panel/host/commands/rename-clip.jsx +127 -0
  164. premiere_cli/panel/host/commands/rename-project-item.jsx +111 -0
  165. premiere_cli/panel/host/commands/rename-track.jsx +84 -0
  166. premiere_cli/panel/host/commands/replace-clip-media.jsx +140 -0
  167. premiere_cli/panel/host/commands/replace-clip.jsx +188 -0
  168. premiere_cli/panel/host/commands/reverse-clip.jsx +187 -0
  169. premiere_cli/panel/host/commands/ripple-delete-clip.jsx +156 -0
  170. premiere_cli/panel/host/commands/roll-edit.jsx +162 -0
  171. premiere_cli/panel/host/commands/save-project-as.jsx +55 -0
  172. premiere_cli/panel/host/commands/save-project.jsx +57 -0
  173. premiere_cli/panel/host/commands/scene-edit-detection.jsx +128 -0
  174. premiere_cli/panel/host/commands/search-project-items.jsx +180 -0
  175. premiere_cli/panel/host/commands/select-all-clips.jsx +107 -0
  176. premiere_cli/panel/host/commands/select-clips-by-color.jsx +142 -0
  177. premiere_cli/panel/host/commands/select-clips-by-name.jsx +133 -0
  178. premiere_cli/panel/host/commands/select-clips-in-range.jsx +133 -0
  179. premiere_cli/panel/host/commands/select-disabled-clips.jsx +127 -0
  180. premiere_cli/panel/host/commands/select-project-item.jsx +115 -0
  181. premiere_cli/panel/host/commands/set-active-project.jsx +184 -0
  182. premiere_cli/panel/host/commands/set-active-sequence.jsx +87 -0
  183. premiere_cli/panel/host/commands/set-all-tracks-targeted.jsx +112 -0
  184. premiere_cli/panel/host/commands/set-anti-alias-quality.jsx +125 -0
  185. premiere_cli/panel/host/commands/set-blend-mode.jsx +111 -0
  186. premiere_cli/panel/host/commands/set-clip-anchor-point.jsx +102 -0
  187. premiere_cli/panel/host/commands/set-clip-enabled.jsx +127 -0
  188. premiere_cli/panel/host/commands/set-clip-opacity.jsx +102 -0
  189. premiere_cli/panel/host/commands/set-clip-pan.jsx +107 -0
  190. premiere_cli/panel/host/commands/set-clip-position.jsx +103 -0
  191. premiere_cli/panel/host/commands/set-clip-properties.jsx +221 -0
  192. premiere_cli/panel/host/commands/set-clip-rotation.jsx +102 -0
  193. premiere_cli/panel/host/commands/set-clip-scale.jsx +102 -0
  194. premiere_cli/panel/host/commands/set-clip-selection.jsx +144 -0
  195. premiere_cli/panel/host/commands/set-clip-speed.jsx +160 -0
  196. premiere_cli/panel/host/commands/set-clip-volume.jsx +128 -0
  197. premiere_cli/panel/host/commands/set-color-label.jsx +123 -0
  198. premiere_cli/panel/host/commands/set-color-value.jsx +155 -0
  199. premiere_cli/panel/host/commands/set-effect-property.jsx +124 -0
  200. premiere_cli/panel/host/commands/set-footage-interpretation.jsx +173 -0
  201. premiere_cli/panel/host/commands/set-frame-blend.jsx +145 -0
  202. premiere_cli/panel/host/commands/set-graphics-white-luminance.jsx +73 -0
  203. premiere_cli/panel/host/commands/set-item-in-out.jsx +180 -0
  204. premiere_cli/panel/host/commands/set-item-metadata.jsx +134 -0
  205. premiere_cli/panel/host/commands/set-item-offline.jsx +109 -0
  206. premiere_cli/panel/host/commands/set-item-start-time.jsx +139 -0
  207. premiere_cli/panel/host/commands/set-keyframe-interpolation.jsx +186 -0
  208. premiere_cli/panel/host/commands/set-override-frame-rate.jsx +123 -0
  209. premiere_cli/panel/host/commands/set-override-pixel-aspect-ratio.jsx +129 -0
  210. premiere_cli/panel/host/commands/set-poster-frame.jsx +192 -0
  211. premiere_cli/panel/host/commands/set-project-panel-metadata.jsx +69 -0
  212. premiere_cli/panel/host/commands/set-scale-to-frame-size.jsx +101 -0
  213. premiere_cli/panel/host/commands/set-scale-width-height.jsx +140 -0
  214. premiere_cli/panel/host/commands/set-sequence-in-out.jsx +65 -0
  215. premiere_cli/panel/host/commands/set-sequence-settings.jsx +184 -0
  216. premiere_cli/panel/host/commands/set-source-in-out.jsx +98 -0
  217. premiere_cli/panel/host/commands/set-target-track.jsx +91 -0
  218. premiere_cli/panel/host/commands/set-time-interpolation.jsx +141 -0
  219. premiere_cli/panel/host/commands/set-track-mute.jsx +91 -0
  220. premiere_cli/panel/host/commands/set-track-visibility.jsx +94 -0
  221. premiere_cli/panel/host/commands/set-transcode-on-ingest.jsx +78 -0
  222. premiere_cli/panel/host/commands/set-uniform-scale.jsx +104 -0
  223. premiere_cli/panel/host/commands/set-work-area.jsx +76 -0
  224. premiere_cli/panel/host/commands/set-workspace.jsx +35 -0
  225. premiere_cli/panel/host/commands/set-xmp-metadata.jsx +157 -0
  226. premiere_cli/panel/host/commands/slide-edit.jsx +162 -0
  227. premiere_cli/panel/host/commands/slip-edit.jsx +167 -0
  228. premiere_cli/panel/host/commands/split-clip.jsx +144 -0
  229. premiere_cli/panel/host/commands/stabilize-clip.jsx +139 -0
  230. premiere_cli/panel/host/commands/stop-playback.jsx +23 -0
  231. premiere_cli/panel/host/commands/trim-clip.jsx +111 -0
  232. premiere_cli/panel/host/commands/undo.jsx +114 -0
  233. premiere_cli/panel/host/commands/unlink-selection.jsx +106 -0
  234. premiere_cli/panel/host/commands/unnest-sequence.jsx +207 -0
  235. premiere_cli/panel/host/commands/update-marker.jsx +120 -0
  236. premiere_cli/panel/host/index.jsx +1227 -0
  237. premiere_cli/panel/host/presets/default-4k-25fps.sqpreset +63 -0
  238. premiere_cli/panel/index.html +16 -0
  239. premiere_cli/panel/js/cs-interface.js +11 -0
  240. premiere_cli/panel/js/main.js +409 -0
  241. premiere_cli/panel_install.py +220 -0
  242. premiere_cli-0.2.0.dist-info/METADATA +127 -0
  243. premiere_cli-0.2.0.dist-info/RECORD +247 -0
  244. premiere_cli-0.2.0.dist-info/WHEEL +5 -0
  245. premiere_cli-0.2.0.dist-info/entry_points.txt +3 -0
  246. premiere_cli-0.2.0.dist-info/licenses/LICENSE +428 -0
  247. premiere_cli-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1227 @@
1
+ // Premiere Bridge — ExtendScript loader and lazy command dispatcher.
2
+ //
3
+ // This file is the manifest's single <ScriptPath>, evaluated eagerly when
4
+ // the panel loads. It defines only the helpers shared by more than one
5
+ // command, plus ppb_dispatch() — each command's actual implementation
6
+ // lives in host/commands/<command-name>.jsx and is $.evalFile'd into this
7
+ // same global context the first time that command is called.
8
+ //
9
+ // Every ppb_-prefixed command function wraps its body in try/catch and
10
+ // always returns a JSON string itself — an uncaught exception here makes
11
+ // evalScript's callback receive only the unhelpful literal string
12
+ // "EvalScript error." with no detail. ppb_dispatch follows the same rule
13
+ // for load/dispatch failures.
14
+
15
+ var TICKS_PER_SECOND = 254016000000;
16
+
17
+ // The QE (Quality Engineering) DOM — app.enableQE(); qe.project... — is
18
+ // undocumented but required for several operations the standard DOM can't
19
+ // do without a dialog (sequence creation from a preset, razor, frame
20
+ // export). See host/QE_DOM_NOTES.md and host/PREMIERE_API_NOTES.md.
21
+ function ensureQEEnabled() {
22
+ if (typeof qe === "undefined") {
23
+ app.enableQE();
24
+ }
25
+ }
26
+
27
+ function findSequenceByName(name) {
28
+ for (var i = 0; i < app.project.sequences.numSequences; i++) {
29
+ if (app.project.sequences[i].name === name) {
30
+ return app.project.sequences[i];
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+
36
+ // A handful of export presets ship inside the Premiere Pro app bundle
37
+ // itself — no need to bundle our own. Paths are relative to the app
38
+ // bundle root and resolved via app.path (the running app's own install
39
+ // location, always readable) rather than scanning /Applications: a
40
+ // directory listing of /Applications from inside the CEP panel returned
41
+ // nothing on this machine — likely macOS TCC/sandboxing blocking that
42
+ // enumeration even though the specific preset files themselves ARE
43
+ // readable.
44
+ function findBundledPresetByRelativePath(relativePath) {
45
+ try {
46
+ var appPath = app.path;
47
+ var bundleEnd = appPath.indexOf(".app");
48
+ if (bundleEnd === -1) {
49
+ return null;
50
+ }
51
+ var bundlePath = appPath.substring(0, bundleEnd + 4); // include ".app"
52
+ var presetFile = new File(bundlePath + "/" + relativePath);
53
+ if (presetFile.exists) {
54
+ return presetFile.fsName;
55
+ }
56
+ } catch (e) {
57
+ // fall through — caller reports "no preset found"
58
+ }
59
+ return null;
60
+ }
61
+
62
+ // seq.setInPoint()/setOutPoint() argument type is inconsistent across
63
+ // Premiere versions in the wild (plain seconds vs. ticks string vs. Time
64
+ // object) — try each form in turn rather than guessing one and failing.
65
+ function trySetSequenceRange(seq, startSeconds, endSeconds) {
66
+ var attempts = [];
67
+
68
+ function attempt(label, inVal, outVal) {
69
+ try {
70
+ seq.setInPoint(inVal);
71
+ seq.setOutPoint(outVal);
72
+ attempts.push({ form: label, success: true });
73
+ return true;
74
+ } catch (e) {
75
+ attempts.push({ form: label, success: false, error: e.toString() });
76
+ return false;
77
+ }
78
+ }
79
+
80
+ if (attempt("seconds", startSeconds, endSeconds)) {
81
+ return { ok: true, attempts: attempts };
82
+ }
83
+
84
+ var startTicks = String(Math.round(startSeconds * TICKS_PER_SECOND));
85
+ var endTicks = String(Math.round(endSeconds * TICKS_PER_SECOND));
86
+ if (attempt("ticksString", startTicks, endTicks)) {
87
+ return { ok: true, attempts: attempts };
88
+ }
89
+
90
+ var startTime = new Time();
91
+ startTime.seconds = startSeconds;
92
+ var endTime = new Time();
93
+ endTime.seconds = endSeconds;
94
+ if (attempt("TimeObject", startTime, endTime)) {
95
+ return { ok: true, attempts: attempts };
96
+ }
97
+
98
+ return { ok: false, attempts: attempts };
99
+ }
100
+
101
+ function getSequenceFps(sequence) {
102
+ var frameDuration = sequence.getSettings().videoFrameRate;
103
+ var ticksPerFrame = parseInt(frameDuration.ticks, 10);
104
+ return Math.round(TICKS_PER_SECOND / ticksPerFrame);
105
+ }
106
+
107
+ function timecodeToSeconds(timecode, fps) {
108
+ var parts = timecode.split(":");
109
+ var minutes = parseInt(parts[0], 10);
110
+ var seconds = parseInt(parts[1], 10);
111
+ var frames = parseInt(parts[2], 10);
112
+ return minutes * 60 + seconds + frames / fps;
113
+ }
114
+
115
+ // Shared clip serializer — used by get-active-sequence, get-full-sequence-
116
+ // info, get-full-clip-info, and get-timeline-summary (all standard-DOM
117
+ // reads, no QE needed). Every field is individually try/caught so one
118
+ // unreadable field never blanks out the rest of the clip. Seconds are read
119
+ // via a Time-like object's `.seconds` first (usually present), falling
120
+ // back to `.ticks` per PREMIERE_API_NOTES.md's ticks-are-strings rule.
121
+ function timeValueToSeconds(timeLike) {
122
+ if (timeLike === null || typeof timeLike === "undefined") {
123
+ return null;
124
+ }
125
+ if (typeof timeLike.seconds === "number") {
126
+ return timeLike.seconds;
127
+ }
128
+ return Number(timeLike.ticks) / TICKS_PER_SECOND;
129
+ }
130
+
131
+ function serializeTrackItem(clip, trackIndex, clipIndex) {
132
+ var out = {
133
+ name: null,
134
+ trackIndex: trackIndex,
135
+ clipIndex: clipIndex,
136
+ nodeId: null,
137
+ startSeconds: null,
138
+ endSeconds: null,
139
+ inPointSeconds: null,
140
+ outPointSeconds: null,
141
+ durationSeconds: null,
142
+ mediaPath: null,
143
+ disabled: null
144
+ };
145
+
146
+ try { out.name = clip.name; } catch (e) { out.name = null; }
147
+ try { out.nodeId = clip.nodeId; } catch (e) { out.nodeId = null; }
148
+ try { out.startSeconds = timeValueToSeconds(clip.start); } catch (e) { out.startSeconds = null; }
149
+ try { out.endSeconds = timeValueToSeconds(clip.end); } catch (e) { out.endSeconds = null; }
150
+ try { out.inPointSeconds = timeValueToSeconds(clip.inPoint); } catch (e) { out.inPointSeconds = null; }
151
+ try { out.outPointSeconds = timeValueToSeconds(clip.outPoint); } catch (e) { out.outPointSeconds = null; }
152
+ try { out.durationSeconds = timeValueToSeconds(clip.duration); } catch (e) { out.durationSeconds = null; }
153
+ try { out.mediaPath = clip.projectItem.getMediaPath(); } catch (e) { out.mediaPath = null; }
154
+ try { out.disabled = clip.disabled; } catch (e) { out.disabled = null; }
155
+
156
+ return out;
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Clip component/property helpers — shared by the wave-3 clip transform and
161
+ // opacity setter commands: set-clip-position, set-clip-scale,
162
+ // set-clip-rotation, set-clip-anchor-point, set-clip-opacity,
163
+ // set-uniform-scale, set-scale-width-height, set-anti-alias-quality, and
164
+ // set-blend-mode. Per PREMIERE_API_NOTES.md's "Effects, transitions,
165
+ // keyframes" section: components[0]=Motion/[1]=Opacity is only a
166
+ // convention, never a guarantee — identify a component by matchName
167
+ // ("AE.ADBE Motion"/"AE.ADBE Opacity") where possible, falling back to
168
+ // displayName. displayName matching is locale-dependent ("Nivel", "Pegel",
169
+ // "音量") — try an exact match first, then a normalized-lowercase match.
170
+ // ---------------------------------------------------------------------------
171
+
172
+ function normalizeDisplayName(name) {
173
+ return String(name).toLowerCase();
174
+ }
175
+
176
+ // Finds a clip component (Motion/Opacity/an applied effect) by matchName
177
+ // first (exact, reliable, locale-independent), falling back to displayName
178
+ // (exact, then normalized-lowercase). matchNames/displayNames are arrays;
179
+ // either may be omitted/empty.
180
+ function findClipComponent(clip, matchNames, displayNames) {
181
+ var numComponents;
182
+ try {
183
+ numComponents = clip.components.numItems;
184
+ } catch (e) {
185
+ return null;
186
+ }
187
+
188
+ if (matchNames && matchNames.length) {
189
+ for (var i = 0; i < numComponents; i++) {
190
+ var comp = clip.components[i];
191
+ var mn = null;
192
+ try { mn = comp.matchName; } catch (e) { mn = null; }
193
+ if (mn) {
194
+ for (var m = 0; m < matchNames.length; m++) {
195
+ if (mn === matchNames[m]) {
196
+ return comp;
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ if (displayNames && displayNames.length) {
204
+ // Exact-match pass.
205
+ for (var i2 = 0; i2 < numComponents; i2++) {
206
+ var comp2 = clip.components[i2];
207
+ var dn2 = null;
208
+ try { dn2 = comp2.displayName; } catch (e) { dn2 = null; }
209
+ if (dn2) {
210
+ for (var d = 0; d < displayNames.length; d++) {
211
+ if (dn2 === displayNames[d]) {
212
+ return comp2;
213
+ }
214
+ }
215
+ }
216
+ }
217
+ // Normalized-lowercase fallback pass (locale-dependent displayName).
218
+ for (var i3 = 0; i3 < numComponents; i3++) {
219
+ var comp3 = clip.components[i3];
220
+ var dn3 = null;
221
+ try { dn3 = comp3.displayName; } catch (e) { dn3 = null; }
222
+ if (dn3) {
223
+ var dn3Lower = normalizeDisplayName(dn3);
224
+ for (var d2 = 0; d2 < displayNames.length; d2++) {
225
+ if (dn3Lower === normalizeDisplayName(displayNames[d2])) {
226
+ return comp3;
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ // Finds a property on a component by displayName — same exact-then-
237
+ // normalized-lowercase matching as findClipComponent.
238
+ function findComponentProperty(component, displayNames) {
239
+ var numProps;
240
+ try {
241
+ numProps = component.properties.numItems;
242
+ } catch (e) {
243
+ return null;
244
+ }
245
+
246
+ for (var i = 0; i < numProps; i++) {
247
+ var prop = component.properties[i];
248
+ var dn = null;
249
+ try { dn = prop.displayName; } catch (e) { dn = null; }
250
+ if (dn) {
251
+ for (var d = 0; d < displayNames.length; d++) {
252
+ if (dn === displayNames[d]) {
253
+ return prop;
254
+ }
255
+ }
256
+ }
257
+ }
258
+
259
+ for (var i2 = 0; i2 < numProps; i2++) {
260
+ var prop2 = component.properties[i2];
261
+ var dn2 = null;
262
+ try { dn2 = prop2.displayName; } catch (e) { dn2 = null; }
263
+ if (dn2) {
264
+ var dn2Lower = normalizeDisplayName(dn2);
265
+ for (var d2 = 0; d2 < displayNames.length; d2++) {
266
+ if (dn2Lower === normalizeDisplayName(displayNames[d2])) {
267
+ return prop2;
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ return null;
274
+ }
275
+
276
+ // Numeric/array-tolerant equality check for verifying a setValue() actually
277
+ // took — floats can round-trip imprecisely, and properties like
278
+ // Position/Anchor Point are two-element [x,y] arrays rather than scalars.
279
+ function valuesApproximatelyEqual(a, b, tolerance) {
280
+ var tol = typeof tolerance === "number" ? tolerance : 0.01;
281
+
282
+ if (a instanceof Array || b instanceof Array) {
283
+ if (!(a instanceof Array) || !(b instanceof Array)) {
284
+ return false;
285
+ }
286
+ if (a.length !== b.length) {
287
+ return false;
288
+ }
289
+ for (var i = 0; i < a.length; i++) {
290
+ if (!valuesApproximatelyEqual(a[i], b[i], tol)) {
291
+ return false;
292
+ }
293
+ }
294
+ return true;
295
+ }
296
+
297
+ if (typeof a === "number" && typeof b === "number") {
298
+ return Math.abs(a - b) <= tol;
299
+ }
300
+
301
+ return a === b;
302
+ }
303
+
304
+ // Sets one property's value, reading it before AND after the mutation.
305
+ // CRITICAL: undo is confirmed non-functional on this Premiere build (see
306
+ // the `undo` command's notes in README.md) — the returned previousValue is
307
+ // the caller's ONLY path to restore a property if a mutation turns out to
308
+ // be unwanted; callers must surface it to the caller/user, never discard
309
+ // it. Returns {found: false} if no property in displayNames exists on the
310
+ // component; otherwise {found: true, previousValue, requestedValue,
311
+ // newValue, verified} where verified is a tolerance-aware comparison of
312
+ // newValue against the requested value (see valuesApproximatelyEqual).
313
+ function setComponentProperty(component, displayNames, value, tolerance) {
314
+ var prop = findComponentProperty(component, displayNames);
315
+ if (!prop) {
316
+ return { found: false };
317
+ }
318
+
319
+ var previousValue = null;
320
+ try { previousValue = prop.getValue(); } catch (e) { previousValue = null; }
321
+
322
+ prop.setValue(value, true);
323
+
324
+ var newValue = null;
325
+ try { newValue = prop.getValue(); } catch (e) { newValue = null; }
326
+
327
+ return {
328
+ found: true,
329
+ previousValue: previousValue,
330
+ requestedValue: value,
331
+ newValue: newValue,
332
+ verified: valuesApproximatelyEqual(newValue, value, tolerance)
333
+ };
334
+ }
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // Shared helpers for wave-3 clip audio/flag mutation commands
338
+ // ---------------------------------------------------------------------------
339
+
340
+ // Makes the resolved sequence active first if it isn't already — required
341
+ // before any QE DOM call, which only ever operates on the active sequence
342
+ // tab (same pattern as get-qe-clip-info/remove-track-intervals).
343
+ function activateSequenceForQE(seq) {
344
+ if (app.project.activeSequence !== seq) {
345
+ app.project.activeSequence = seq;
346
+ }
347
+ }
348
+
349
+ // Finds a clip's Volume component's Level property (audioVolume/Level) —
350
+ // shared by set-clip-volume, adjust-audio-levels, add-audio-keyframes.
351
+ // Returns the property object, or null if not found (e.g. not an audio
352
+ // clip, or this Premiere build's Volume component uses a different
353
+ // displayName — see PREMIERE_API_NOTES.md's locale-dependent-matching
354
+ // caveat, though we only probe the English name here).
355
+ function findVolumeLevelProperty(clip) {
356
+ try {
357
+ for (var i = 0; i < clip.components.numItems; i++) {
358
+ var comp = clip.components[i];
359
+ var compName = null;
360
+ var compMatch = null;
361
+ try { compName = comp.displayName; } catch (e) { compName = null; }
362
+ try { compMatch = comp.matchName; } catch (e) { compMatch = null; }
363
+ if (compName === "Volume" || compMatch === "audioVolume") {
364
+ for (var p = 0; p < comp.properties.numItems; p++) {
365
+ var propName = null;
366
+ try { propName = comp.properties[p].displayName; } catch (e) { propName = null; }
367
+ if (propName === "Level") {
368
+ return comp.properties[p];
369
+ }
370
+ }
371
+ }
372
+ }
373
+ } catch (e) {
374
+ return null;
375
+ }
376
+ return null;
377
+ }
378
+
379
+ // Finds a clip's Panner component's Balance/Pan property — shared by
380
+ // set-clip-pan. Component is usually "Panner" with a "Balance" property
381
+ // (mono-to-stereo pan); some builds/locales may expose "Pan" instead — both
382
+ // are probed, same defensive approach as the reference tool.
383
+ function findPannerProperty(clip) {
384
+ try {
385
+ for (var i = 0; i < clip.components.numItems; i++) {
386
+ var comp = clip.components[i];
387
+ var compName = null;
388
+ try { compName = comp.displayName; } catch (e) { compName = null; }
389
+ if (compName === "Panner") {
390
+ for (var p = 0; p < comp.properties.numItems; p++) {
391
+ var propName = null;
392
+ try { propName = comp.properties[p].displayName; } catch (e) { propName = null; }
393
+ if (propName === "Balance" || propName === "Pan") {
394
+ return comp.properties[p];
395
+ }
396
+ }
397
+ }
398
+ }
399
+ } catch (e) {
400
+ return null;
401
+ }
402
+ return null;
403
+ }
404
+
405
+ // dB <-> linear-amplitude conversion for the audio Level property, per
406
+ // hetpatel's empirically-calibrated formula for this build family
407
+ // (PREMIERE_API_NOTES.md: "displayed 0 dB ~= internal 0.17783",
408
+ // linear = 10^((dB-15)/20)). leancoderkavy's own repo uses the simpler,
409
+ // UNCALIBRATED 10^(dB/20) instead, and ayushozha mostly passes raw dB
410
+ // straight through — the three reference repos do NOT agree, and none of
411
+ // this has been verified against our own Premiere build (no live audio
412
+ // clip available at authoring time). Treat every dB value that passes
413
+ // through these two functions as approximate until calibrated live.
414
+ function dbToLinearCalibrated(db) {
415
+ return Math.pow(10, (db - 15) / 20);
416
+ }
417
+ function linearToDbCalibrated(linear) {
418
+ if (!(linear > 0)) {
419
+ return null;
420
+ }
421
+ return 20 * Math.log(linear) / Math.LN10 + 15;
422
+ }
423
+
424
+ // QE track item lists interleave "Empty" gap items between real clips
425
+ // (PREMIERE_API_NOTES.md / QE_DOM_NOTES.md) — the standard DOM's Nth clip
426
+ // on a track is the Nth item whose QE .type is NOT "Empty" (matches our own
427
+ // confirmed finding, not just the reference repos' claim). Shared by
428
+ // set-frame-blend and set-time-interpolation, which both need to locate the
429
+ // QE clip corresponding to a standard-DOM clipIndex.
430
+ function qeFindNthNonEmptyClip(qeTrack, clipIndex) {
431
+ var seen = -1;
432
+ var numItems = qeTrack.numItems;
433
+ for (var i = 0; i < numItems; i++) {
434
+ var item = qeTrack.getItemAt(i);
435
+ var itemType = null;
436
+ try { itemType = item.type; } catch (e) { itemType = null; }
437
+ if (itemType === "Empty") {
438
+ continue;
439
+ }
440
+ seen++;
441
+ if (seen === clipIndex) {
442
+ return item;
443
+ }
444
+ }
445
+ return null;
446
+ }
447
+
448
+ // ---------------------------------------------------------------------------
449
+ // Wave-4 effect/component helpers — shared by the effect apply/remove/copy
450
+ // commands (apply-effect, apply-audio-effect, remove-effect,
451
+ // remove-effect-by-name, remove-all-effects, color-correct, apply-lut,
452
+ // stabilize-clip, copy-effects-between-clips, copy-effect-values,
453
+ // batch-apply-effect). Builds on findClipComponent/findComponentProperty/
454
+ // setComponentProperty/valuesApproximatelyEqual/qeFindNthNonEmptyClip above.
455
+ // ---------------------------------------------------------------------------
456
+
457
+ // Resolves a standard-DOM timeline clip from trackType/trackIndex/clipIndex,
458
+ // with the same out-of-range error messages set-frame-blend uses. Returns
459
+ // {clip: <TrackItem>} on success or {error: "..."} on failure — never
460
+ // throws.
461
+ function resolveTimelineClip(seq, trackType, trackIndex, clipIndex) {
462
+ var trackCollection = trackType === "video" ? seq.videoTracks : seq.audioTracks;
463
+ var numTracks = trackCollection.numTracks;
464
+ if (trackIndex >= numTracks) {
465
+ return { error: "trackIndex " + trackIndex + " is out of range — sequence has " + numTracks + " " + trackType + " track(s)" };
466
+ }
467
+ var track = trackCollection[trackIndex];
468
+ var numClips = track.clips.numItems;
469
+ if (clipIndex >= numClips) {
470
+ return { error: "clipIndex " + clipIndex + " is out of range — track " + trackIndex + " has " + numClips + " clip(s)" };
471
+ }
472
+ return { clip: track.clips[clipIndex] };
473
+ }
474
+
475
+ // Resolves the QE-DOM clip corresponding to a standard-DOM
476
+ // trackType/trackIndex/clipIndex, on an already-activated qeSeq (see
477
+ // activateSequenceForQE). Returns {qeClip, qeTrack} on success or
478
+ // {error: "..."} on failure — same pattern as set-frame-blend's inline
479
+ // lookup, factored out for reuse across the wave-4 effect commands.
480
+ function resolveQeClip(qeSeq, trackType, trackIndex, clipIndex) {
481
+ var qeTrack;
482
+ try {
483
+ qeTrack = trackType === "video" ? qeSeq.getVideoTrackAt(trackIndex) : qeSeq.getAudioTrackAt(trackIndex);
484
+ } catch (e) {
485
+ return { error: "QE track lookup failed: " + e.toString() };
486
+ }
487
+ if (!qeTrack) {
488
+ return { error: "QE track not found at index " + trackIndex };
489
+ }
490
+ var qeClip;
491
+ try {
492
+ qeClip = qeFindNthNonEmptyClip(qeTrack, clipIndex);
493
+ } catch (e) {
494
+ return { error: "QE clip lookup failed: " + e.toString() };
495
+ }
496
+ if (!qeClip) {
497
+ return { error: "could not locate the corresponding non-Empty QE clip at clipIndex " + clipIndex };
498
+ }
499
+ return { qeClip: qeClip, qeTrack: qeTrack };
500
+ }
501
+
502
+ // Serializes a clip's components collection to a plain array of
503
+ // {displayName, matchName} — the "before"/"after" snapshot every mutating
504
+ // effect command reports per the wave-4 verification rule (read
505
+ // clip.components before/after, report counts AND names).
506
+ function serializeClipComponents(clip) {
507
+ var out = [];
508
+ try {
509
+ var numComponents = clip.components.numItems;
510
+ for (var i = 0; i < numComponents; i++) {
511
+ var comp = clip.components[i];
512
+ var dn = null;
513
+ var mn = null;
514
+ try { dn = comp.displayName; } catch (e) { dn = null; }
515
+ try { mn = comp.matchName; } catch (e) { mn = null; }
516
+ out.push({ displayName: dn, matchName: mn });
517
+ }
518
+ } catch (e) {
519
+ // fall through — caller sees an empty array, not a thrown error
520
+ }
521
+ return out;
522
+ }
523
+
524
+ // Looks up a video effect by name in the QE catalog — getVideoEffectByName
525
+ // first (per PREMIERE_API_NOTES.md's documented "universal dance"), falling
526
+ // back to a linear scan of getVideoEffectList() in case the by-name lookup
527
+ // itself is unavailable/unreliable on this build. Returns the QE effect
528
+ // object, or null if not found by either path.
529
+ function findQeVideoEffectByName(name) {
530
+ try {
531
+ var fx = qe.project.getVideoEffectByName(name);
532
+ if (fx) {
533
+ return fx;
534
+ }
535
+ } catch (e) {
536
+ // fall through to list scan
537
+ }
538
+ try {
539
+ var list = qe.project.getVideoEffectList();
540
+ for (var i = 0; i < list.numItems; i++) {
541
+ if (list[i].name === name) {
542
+ return list[i];
543
+ }
544
+ }
545
+ } catch (e) {
546
+ // fall through — caller reports "not found"
547
+ }
548
+ return null;
549
+ }
550
+
551
+ // Audio counterpart of findQeVideoEffectByName.
552
+ function findQeAudioEffectByName(name) {
553
+ try {
554
+ var fx = qe.project.getAudioEffectByName(name);
555
+ if (fx) {
556
+ return fx;
557
+ }
558
+ } catch (e) {
559
+ // fall through to list scan
560
+ }
561
+ try {
562
+ var list = qe.project.getAudioEffectList();
563
+ for (var i = 0; i < list.numItems; i++) {
564
+ if (list[i].name === name) {
565
+ return list[i];
566
+ }
567
+ }
568
+ } catch (e) {
569
+ // fall through — caller reports "not found"
570
+ }
571
+ return null;
572
+ }
573
+
574
+ // Built-in components no removal command should ever touch — checked by
575
+ // BOTH matchName (reliable, locale-independent) and displayName (fallback,
576
+ // locale-dependent) per remove-effect's "protect the built-ins" rule.
577
+ function isProtectedBuiltinComponent(comp) {
578
+ var mn = null;
579
+ var dn = null;
580
+ try { mn = comp.matchName; } catch (e) { mn = null; }
581
+ try { dn = comp.displayName; } catch (e) { dn = null; }
582
+
583
+ var protectedMatchNames = ["AE.ADBE Motion", "AE.ADBE Opacity"];
584
+ for (var i = 0; i < protectedMatchNames.length; i++) {
585
+ if (mn === protectedMatchNames[i]) {
586
+ return true;
587
+ }
588
+ }
589
+ var protectedDisplayNames = ["Motion", "Opacity"];
590
+ for (var j = 0; j < protectedDisplayNames.length; j++) {
591
+ if (dn === protectedDisplayNames[j]) {
592
+ return true;
593
+ }
594
+ }
595
+ return false;
596
+ }
597
+
598
+ // Intrinsic/non-effect component display names — copy-effects-between-clips
599
+ // skips these when no effectName filter narrows the copy to one effect
600
+ // (matches leancoderkavy's clipboard.ts intrinsic list).
601
+ var INTRINSIC_COMPONENT_DISPLAY_NAMES = ["Motion", "Opacity", "Time Remapping", "Volume", "Channel Volume", "Panner"];
602
+
603
+ function isIntrinsicComponentDisplayName(name) {
604
+ for (var i = 0; i < INTRINSIC_COMPONENT_DISPLAY_NAMES.length; i++) {
605
+ if (name === INTRINSIC_COMPONENT_DISPLAY_NAMES[i]) {
606
+ return true;
607
+ }
608
+ }
609
+ return false;
610
+ }
611
+
612
+ // Finds (and applies via QE if missing) the Lumetri Color component on a
613
+ // clip — shared by color-correct and apply-lut, both of which need it
614
+ // present before setting properties. seq must already be the clip's own
615
+ // sequence; QE will be activated on it if the effect needs applying.
616
+ // Returns {component, applied} on success (applied = true if this call had
617
+ // to add it) or {error: "..."} on failure.
618
+ function ensureLumetriColorComponent(seq, trackType, trackIndex, clipIndex, clip) {
619
+ var existing = findClipComponent(clip, ["AE.ADBE Lumetri"], ["Lumetri Color"]);
620
+ if (existing) {
621
+ return { component: existing, applied: false };
622
+ }
623
+
624
+ try {
625
+ ensureQEEnabled();
626
+ activateSequenceForQE(seq);
627
+ } catch (e) {
628
+ return { error: "app.enableQE()/sequence activation failed: " + e.toString() };
629
+ }
630
+ if (typeof qe === "undefined" || !qe.project) {
631
+ return { error: "QE DOM not available after enableQE()" };
632
+ }
633
+
634
+ var qeSeq;
635
+ try {
636
+ qeSeq = qe.project.getActiveSequence();
637
+ } catch (e) {
638
+ return { error: "qe.project.getActiveSequence() failed: " + e.toString() };
639
+ }
640
+
641
+ var qeResolved = resolveQeClip(qeSeq, trackType, trackIndex, clipIndex);
642
+ if (qeResolved.error) {
643
+ return { error: qeResolved.error };
644
+ }
645
+
646
+ var fx = findQeVideoEffectByName("Lumetri Color");
647
+ if (!fx) {
648
+ return { error: "Lumetri Color effect not found in this Premiere install's QE effect catalog" };
649
+ }
650
+
651
+ try {
652
+ qeResolved.qeClip.addVideoEffect(fx);
653
+ } catch (e) {
654
+ return { error: "qeClip.addVideoEffect(Lumetri Color) failed: " + e.toString() };
655
+ }
656
+
657
+ var reFound = findClipComponent(clip, ["AE.ADBE Lumetri"], ["Lumetri Color"]);
658
+ if (!reFound) {
659
+ return { error: "Lumetri Color was applied via QE but could not be re-found on the standard-DOM clip afterward" };
660
+ }
661
+ return { component: reFound, applied: true };
662
+ }
663
+
664
+ // ---------------------------------------------------------------------------
665
+ // Keyframe/effect-property helpers — shared by wave-4's get-effect-properties,
666
+ // set-effect-property, get-keyframes, add-keyframe, remove-keyframe,
667
+ // remove-keyframe-range, set-keyframe-interpolation, get-value-at-time, and
668
+ // set-color-value. Per PREMIERE_API_NOTES.md's Property API section: a
669
+ // property's key TIME argument is Time objects on some Premiere builds and
670
+ // plain seconds on others (version drift) — tryTimeForms() below tries
671
+ // ticksString, then a Time object, then a raw seconds number, recording an
672
+ // `attempts` array so callers can see exactly what worked, same spirit as
673
+ // index.jsx's own trySetSequenceRange(). Component lookup for these commands
674
+ // is deliberately GENERIC (any component, not just Motion/Opacity) — callers
675
+ // pass an arbitrary componentName as BOTH the matchName and displayName
676
+ // candidate list to the existing findClipComponent() helper above.
677
+ // ---------------------------------------------------------------------------
678
+
679
+ function secondsToTicksString(seconds) {
680
+ return String(Math.round(seconds * TICKS_PER_SECOND));
681
+ }
682
+
683
+ function secondsToTimeObject(seconds) {
684
+ var t = new Time();
685
+ t.seconds = seconds;
686
+ return t;
687
+ }
688
+
689
+ // Calls fn(timeArg) trying, in order: a ticks string, a Time object, then a
690
+ // raw seconds number — stopping at the first form fn() doesn't throw on.
691
+ // fn's return value (if any) is carried through as `result`. Returns
692
+ // {success, result?, attempts, formUsed?} — attempts is always populated,
693
+ // even on total failure, so the caller can report exactly what was tried.
694
+ function tryTimeForms(seconds, fn) {
695
+ var attempts = [];
696
+ var forms = [
697
+ { label: "ticksString", value: secondsToTicksString(seconds) },
698
+ { label: "TimeObject", value: secondsToTimeObject(seconds) },
699
+ { label: "seconds", value: seconds }
700
+ ];
701
+ for (var i = 0; i < forms.length; i++) {
702
+ try {
703
+ var result = fn(forms[i].value);
704
+ attempts.push({ form: forms[i].label, success: true });
705
+ return { success: true, result: result, attempts: attempts, formUsed: forms[i].label };
706
+ } catch (e) {
707
+ attempts.push({ form: forms[i].label, success: false, error: e.toString() });
708
+ }
709
+ }
710
+ return { success: false, attempts: attempts };
711
+ }
712
+
713
+ // Normalizes a key time value returned by prop.getKeys()/etc. to seconds.
714
+ // Per PREMIERE_API_NOTES.md, key times are DISPUTED across builds: Time
715
+ // objects (handled by the existing timeValueToSeconds()), or — per some
716
+ // repos/builds — a raw ticks number/string, or a raw seconds number/string.
717
+ // A raw numeric/string value is heuristically treated as ticks if it's
718
+ // larger than 1,000,000 (no realistic sequence position is that many
719
+ // seconds — over 11 days — while ticks routinely exceed it), else as plain
720
+ // seconds. This heuristic is UNVERIFIED against a live keyframed property.
721
+ function keyTimeToSeconds(keyTime) {
722
+ if (keyTime === null || typeof keyTime === "undefined") {
723
+ return null;
724
+ }
725
+ if (typeof keyTime === "number") {
726
+ return keyTime > 1000000 ? (keyTime / TICKS_PER_SECOND) : keyTime;
727
+ }
728
+ if (typeof keyTime === "string") {
729
+ var n = Number(keyTime);
730
+ if (isNaN(n)) {
731
+ return null;
732
+ }
733
+ return n > 1000000 ? (n / TICKS_PER_SECOND) : n;
734
+ }
735
+ return timeValueToSeconds(keyTime);
736
+ }
737
+
738
+ // ---------------------------------------------------------------------------
739
+ // Shared helper for wave-4 transition mutation commands (add-transition,
740
+ // batch-add-transitions) — see PREMIERE_API_NOTES.md's "Transitions (QE
741
+ // only)" line: qe.project.getVideoTransitionByName(name) resolves the
742
+ // transition, then addTransition() is called with a DISPUTED argument
743
+ // count/duration format across the three reference repos (3-7 args;
744
+ // "frames:00" timecode string vs seconds-string vs ticks string), on
745
+ // EITHER the QE clip or the QE track depending on which repo you read.
746
+ // Rather than pick one, every plausible form is tried in turn and success
747
+ // is verified the only reliable way available: a numItems delta on the
748
+ // STANDARD DOM's track.transitions collection (QE addTransition's own
749
+ // return value is not trustworthy — same lesson as export-frame's
750
+ // exportFramePNG). Video-only: no reference repo demonstrates an
751
+ // audio-transition "add" API, only add-transition's video enumeration
752
+ // (list-available-transitions) and audio's own list-only command.
753
+ // ---------------------------------------------------------------------------
754
+
755
+ function ppbGetTrackTransitionsCount(track) {
756
+ try {
757
+ return track.transitions.numItems;
758
+ } catch (e) {
759
+ return null;
760
+ }
761
+ }
762
+
763
+ function ppbReadLastTransitionName(track) {
764
+ try {
765
+ var n = track.transitions.numItems;
766
+ if (n <= 0) {
767
+ return null;
768
+ }
769
+ var t = track.transitions[n - 1];
770
+ try { return t.name; } catch (e) { return null; }
771
+ } catch (e2) {
772
+ return null;
773
+ }
774
+ }
775
+
776
+ // Applies one transition to one clip's start/end, addressed the same way
777
+ // as get-full-clip-info (trackIndex/clipIndex on seq.videoTracks — video
778
+ // only). `transitionQE` is the already-resolved QE transition object (or
779
+ // null for "default transition", per ayushozha's addTransition(null, true,
780
+ // "1.0") convention). Returns a plain object — never throws; every
781
+ // failure mode is captured in the returned shape so callers (add-
782
+ // transition, batch-add-transitions) can report per-clip results
783
+ // uniformly.
784
+ function ppbApplyTransitionToClip(seq, trackIndex, clipIndex, transitionQE, atEnd, durationSeconds) {
785
+ var out = {
786
+ trackIndex: trackIndex,
787
+ clipIndex: clipIndex,
788
+ clipName: null,
789
+ previousCount: null,
790
+ newCount: null,
791
+ verified: false,
792
+ attempts: [],
793
+ succeededWithArgs: null,
794
+ addedTransitionName: null,
795
+ error: null
796
+ };
797
+
798
+ var numTracks;
799
+ try { numTracks = seq.videoTracks.numTracks; } catch (e) { numTracks = 0; }
800
+ if (trackIndex >= numTracks) {
801
+ out.error = "trackIndex " + trackIndex + " is out of range — sequence has " + numTracks + " video track(s)";
802
+ return out;
803
+ }
804
+ var track = seq.videoTracks[trackIndex];
805
+ var numClips;
806
+ try { numClips = track.clips.numItems; } catch (e2) { numClips = 0; }
807
+ if (clipIndex >= numClips) {
808
+ out.error = "clipIndex " + clipIndex + " is out of range — track " + trackIndex + " has " + numClips + " clip(s)";
809
+ return out;
810
+ }
811
+
812
+ var clip = track.clips[clipIndex];
813
+ try { out.clipName = clip.name; } catch (e3) { out.clipName = null; }
814
+
815
+ out.previousCount = ppbGetTrackTransitionsCount(track);
816
+
817
+ try {
818
+ ensureQEEnabled();
819
+ activateSequenceForQE(seq);
820
+ } catch (e4) {
821
+ out.error = "app.enableQE()/sequence activation failed: " + e4.toString();
822
+ return out;
823
+ }
824
+ if (typeof qe === "undefined" || !qe.project) {
825
+ out.error = "QE DOM not available after enableQE()";
826
+ return out;
827
+ }
828
+
829
+ var qeSeq, qeTrack, qeClip;
830
+ try {
831
+ qeSeq = qe.project.getActiveSequence();
832
+ qeTrack = qeSeq.getVideoTrackAt(trackIndex);
833
+ qeClip = qeFindNthNonEmptyClip(qeTrack, clipIndex);
834
+ } catch (e5) {
835
+ out.error = "QE track/clip lookup failed: " + e5.toString();
836
+ return out;
837
+ }
838
+ if (!qeTrack || !qeClip) {
839
+ out.error = "could not locate the corresponding QE track/clip";
840
+ return out;
841
+ }
842
+
843
+ var secondsStr = String(durationSeconds);
844
+ var ticksStr = String(Math.round(durationSeconds * TICKS_PER_SECOND));
845
+ var cutTicksStr = null;
846
+ try { cutTicksStr = (atEnd ? clip.end.ticks : clip.start.ticks).toString(); } catch (e6) { cutTicksStr = null; }
847
+
848
+ function attempt(label, fn) {
849
+ if (out.verified) {
850
+ return;
851
+ }
852
+ var entry = { form: label };
853
+ try {
854
+ fn();
855
+ entry.success = true;
856
+ } catch (e7) {
857
+ entry.success = false;
858
+ entry.error = e7.toString();
859
+ out.attempts.push(entry);
860
+ return;
861
+ }
862
+ var newCount = ppbGetTrackTransitionsCount(track);
863
+ if (newCount !== null && out.previousCount !== null && newCount > out.previousCount) {
864
+ entry.verifiedCount = newCount;
865
+ out.verified = true;
866
+ out.newCount = newCount;
867
+ out.succeededWithArgs = label;
868
+ out.addedTransitionName = ppbReadLastTransitionName(track);
869
+ } else {
870
+ entry.note = "call did not throw, but transitions count did not increase — treating as unverified";
871
+ }
872
+ out.attempts.push(entry);
873
+ }
874
+
875
+ // qeClip-level forms (per PREMIERE_API_NOTES.md's headline signature).
876
+ attempt("qeClip.addTransition(tr, atEnd, secondsString)", function () {
877
+ qeClip.addTransition(transitionQE, atEnd, secondsStr);
878
+ });
879
+ attempt("qeClip.addTransition(tr, atEnd, ticksString)", function () {
880
+ qeClip.addTransition(transitionQE, atEnd, ticksStr);
881
+ });
882
+ attempt("qeClip.addTransition(tr, atEnd, secondsString, \"0\")", function () {
883
+ qeClip.addTransition(transitionQE, atEnd, secondsStr, "0");
884
+ });
885
+ attempt("qeClip.addTransition(tr, atEnd, ticksString, \"0\", false)", function () {
886
+ qeClip.addTransition(transitionQE, atEnd, ticksStr, "0", false);
887
+ });
888
+ attempt("qeClip.addTransition(tr, atEnd, durationSecondsNumber)", function () {
889
+ qeClip.addTransition(transitionQE, atEnd, durationSeconds);
890
+ });
891
+ // qeTrack-level forms (leancoderkavy's own reference implementation —
892
+ // cut-point ticks + duration ticks + alignment + reverse booleans).
893
+ if (cutTicksStr !== null) {
894
+ attempt("qeTrack.addTransition(tr, atEnd, cutTicks, durationTicks, \"0\", false)", function () {
895
+ qeTrack.addTransition(transitionQE, atEnd, cutTicksStr, ticksStr, "0", false);
896
+ });
897
+ attempt("qeTrack.addTransition(tr, atEnd, cutTicks, durationSeconds, \"0\", false)", function () {
898
+ qeTrack.addTransition(transitionQE, atEnd, cutTicksStr, secondsStr, "0", false);
899
+ });
900
+ }
901
+
902
+ if (!out.verified) {
903
+ out.error = "could not add the transition with any known argument form — see attempts";
904
+ }
905
+
906
+ return out;
907
+ }
908
+
909
+ // ---------------------------------------------------------------------------
910
+ // Lazy command dispatch
911
+ // ---------------------------------------------------------------------------
912
+
913
+ // Command name → { file under host/commands/, ppb_ function it defines }.
914
+ // To add a command: create host/commands/<name>.jsx defining the function,
915
+ // add a line here, and add the command name to main.js's ALLOWED_COMMANDS.
916
+ var PPB_COMMANDS = {
917
+ "create-sequence": { file: "create-sequence.jsx", fn: "ppb_createSequence" },
918
+ "extract-audio-track": { file: "extract-audio-track.jsx", fn: "ppb_extractAudioTrack" },
919
+ "remove-track-intervals": { file: "remove-track-intervals.jsx", fn: "ppb_removeTrackIntervals" },
920
+ "export-frame": { file: "export-frame.jsx", fn: "ppb_exportFrame" },
921
+ "get-project-info": { file: "get-project-info.jsx", fn: "ppb_getProjectInfo" },
922
+ "list-project-items": { file: "list-project-items.jsx", fn: "ppb_listProjectItems" },
923
+ "get-full-project-overview": { file: "get-full-project-overview.jsx", fn: "ppb_getFullProjectOverview" },
924
+ "search-project-items": { file: "search-project-items.jsx", fn: "ppb_searchProjectItems" },
925
+ "get-active-sequence": { file: "get-active-sequence.jsx", fn: "ppb_getActiveSequence" },
926
+ "get-full-sequence-info": { file: "get-full-sequence-info.jsx", fn: "ppb_getFullSequenceInfo" },
927
+ "get-full-clip-info": { file: "get-full-clip-info.jsx", fn: "ppb_getFullClipInfo" },
928
+ "get-timeline-summary": { file: "get-timeline-summary.jsx", fn: "ppb_getTimelineSummary" },
929
+ "set-clip-volume": { file: "set-clip-volume.jsx", fn: "ppb_setClipVolume" },
930
+ "set-clip-pan": { file: "set-clip-pan.jsx", fn: "ppb_setClipPan" },
931
+ "adjust-audio-levels": { file: "adjust-audio-levels.jsx", fn: "ppb_adjustAudioLevels" },
932
+ "add-audio-keyframes": { file: "add-audio-keyframes.jsx", fn: "ppb_addAudioKeyframes" },
933
+ "rename-clip": { file: "rename-clip.jsx", fn: "ppb_renameClip" },
934
+ "batch-rename-clips": { file: "batch-rename-clips.jsx", fn: "ppb_batchRenameClips" },
935
+ "set-clip-enabled": { file: "set-clip-enabled.jsx", fn: "ppb_setClipEnabled" },
936
+ "batch-set-clips-enabled": { file: "batch-set-clips-enabled.jsx", fn: "ppb_batchSetClipsEnabled" },
937
+ "set-frame-blend": { file: "set-frame-blend.jsx", fn: "ppb_setFrameBlend" },
938
+ "set-time-interpolation": { file: "set-time-interpolation.jsx", fn: "ppb_setTimeInterpolation" },
939
+ "set-clip-properties": { file: "set-clip-properties.jsx", fn: "ppb_setClipProperties" },
940
+ "set-item-metadata": { file: "set-item-metadata.jsx", fn: "ppb_setItemMetadata" },
941
+ "set-color-label": { file: "set-color-label.jsx", fn: "ppb_setColorLabel" },
942
+ "set-footage-interpretation": { file: "set-footage-interpretation.jsx", fn: "ppb_setFootageInterpretation" },
943
+ "set-xmp-metadata": { file: "set-xmp-metadata.jsx", fn: "ppb_setXmpMetadata" },
944
+ "get-effect-properties": { file: "get-effect-properties.jsx", fn: "ppb_getEffectProperties" },
945
+ "set-effect-property": { file: "set-effect-property.jsx", fn: "ppb_setEffectProperty" },
946
+ "get-keyframes": { file: "get-keyframes.jsx", fn: "ppb_getKeyframes" },
947
+ "add-keyframe": { file: "add-keyframe.jsx", fn: "ppb_addKeyframe" },
948
+ "remove-keyframe": { file: "remove-keyframe.jsx", fn: "ppb_removeKeyframe" },
949
+ "remove-keyframe-range": { file: "remove-keyframe-range.jsx", fn: "ppb_removeKeyframeRange" },
950
+ "set-keyframe-interpolation": { file: "set-keyframe-interpolation.jsx", fn: "ppb_setKeyframeInterpolation" },
951
+ "get-value-at-time": { file: "get-value-at-time.jsx", fn: "ppb_getValueAtTime" },
952
+ "set-color-value": { file: "set-color-value.jsx", fn: "ppb_setColorValue" },
953
+ "add-transition": { file: "add-transition.jsx", fn: "ppb_addTransition" },
954
+ "batch-add-transitions": { file: "batch-add-transitions.jsx", fn: "ppb_batchAddTransitions" },
955
+ "remove-transition": { file: "remove-transition.jsx", fn: "ppb_removeTransition" },
956
+ "ripple-delete-clip": { file: "ripple-delete-clip.jsx", fn: "ppb_rippleDeleteClip" },
957
+ "roll-edit": { file: "roll-edit.jsx", fn: "ppb_rollEdit" },
958
+ "slide-edit": { file: "slide-edit.jsx", fn: "ppb_slideEdit" },
959
+ "slip-edit": { file: "slip-edit.jsx", fn: "ppb_slipEdit" },
960
+ "move-clip-to-track": { file: "move-clip-to-track.jsx", fn: "ppb_moveClipToTrack" },
961
+ "reverse-clip": { file: "reverse-clip.jsx", fn: "ppb_reverseClip" },
962
+ "link-selection": { file: "link-selection.jsx", fn: "ppb_linkSelection" },
963
+ "unlink-selection": { file: "unlink-selection.jsx", fn: "ppb_unlinkSelection" },
964
+ "overwrite-clip-at": { file: "overwrite-clip-at.jsx", fn: "ppb_overwriteClipAt" },
965
+ "razor-all-tracks": { file: "razor-all-tracks.jsx", fn: "ppb_razorAllTracks" },
966
+ "set-item-in-out": { file: "set-item-in-out.jsx", fn: "ppb_setItemInOut" },
967
+ "clear-item-in-out": { file: "clear-item-in-out.jsx", fn: "ppb_clearItemInOut" },
968
+ "clear-sequence-in-out": { file: "clear-sequence-in-out.jsx", fn: "ppb_clearSequenceInOut" },
969
+ "remove-selected-clips": { file: "remove-selected-clips.jsx", fn: "ppb_removeSelectedClips" },
970
+ "lift-selection": { file: "lift-selection.jsx", fn: "ppb_liftSelection" },
971
+ "extract-selection": { file: "extract-selection.jsx", fn: "ppb_extractSelection" },
972
+ "nest-clips": { file: "nest-clips.jsx", fn: "ppb_nestClips" },
973
+ "freeze-frame": { file: "freeze-frame.jsx", fn: "ppb_freezeFrame" },
974
+ "match-frame": { file: "match-frame.jsx", fn: "ppb_matchFrame" },
975
+ "add-adjustment-layer": { file: "add-adjustment-layer.jsx", fn: "ppb_addAdjustmentLayer" },
976
+ "unnest-sequence": { file: "unnest-sequence.jsx", fn: "ppb_unnestSequence" },
977
+ "save-project": { file: "save-project.jsx", fn: "ppb_saveProject" },
978
+ "save-project-as": { file: "save-project-as.jsx", fn: "ppb_saveProjectAs" },
979
+ "open-project": { file: "open-project.jsx", fn: "ppb_openProject" },
980
+ "set-active-sequence": { file: "set-active-sequence.jsx", fn: "ppb_setActiveSequence" },
981
+ "find-items-by-media-path": { file: "find-items-by-media-path.jsx", fn: "ppb_findItemsByMediaPath" },
982
+ "create-smart-bin": { file: "create-smart-bin.jsx", fn: "ppb_createSmartBin" },
983
+ "add-custom-metadata-field": { file: "add-custom-metadata-field.jsx", fn: "ppb_addCustomMetadataField" },
984
+ "import-sequences-from-project": { file: "import-sequences-from-project.jsx", fn: "ppb_importSequencesFromProject" },
985
+ "import-fcp-xml": { file: "import-fcp-xml.jsx", fn: "ppb_importFcpXml" },
986
+ "import-ae-comps": { file: "import-ae-comps.jsx", fn: "ppb_importAeComps" },
987
+ "create-bars-and-tone": { file: "create-bars-and-tone.jsx", fn: "ppb_createBarsAndTone" },
988
+ "set-transcode-on-ingest": { file: "set-transcode-on-ingest.jsx", fn: "ppb_setTranscodeOnIngest" },
989
+ "set-project-panel-metadata": { file: "set-project-panel-metadata.jsx", fn: "ppb_setProjectPanelMetadata" },
990
+ "get-graphics-white-luminance": { file: "get-graphics-white-luminance.jsx", fn: "ppb_getGraphicsWhiteLuminance" },
991
+ "set-graphics-white-luminance": { file: "set-graphics-white-luminance.jsx", fn: "ppb_setGraphicsWhiteLuminance" },
992
+ "duplicate-sequence": { file: "duplicate-sequence.jsx", fn: "ppb_duplicateSequence" },
993
+ "set-sequence-settings": { file: "set-sequence-settings.jsx", fn: "ppb_setSequenceSettings" },
994
+ "create-subsequence": { file: "create-subsequence.jsx", fn: "ppb_createSubsequence" },
995
+ "auto-reframe-sequence": { file: "auto-reframe-sequence.jsx", fn: "ppb_autoReframeSequence" },
996
+ "create-sequence-from-preset": { file: "create-sequence-from-preset.jsx", fn: "ppb_createSequenceFromPreset" },
997
+ "create-sequence-from-clips": { file: "create-sequence-from-clips.jsx", fn: "ppb_createSequenceFromClips" },
998
+ "attach-custom-property": { file: "attach-custom-property.jsx", fn: "ppb_attachCustomProperty" },
999
+ "close-sequence": { file: "close-sequence.jsx", fn: "ppb_closeSequence" },
1000
+ "export-sequence-as-project": { file: "export-sequence-as-project.jsx", fn: "ppb_exportSequenceAsProject" },
1001
+ "scene-edit-detection": { file: "scene-edit-detection.jsx", fn: "ppb_sceneEditDetection" },
1002
+ "export-sequence": { file: "export-sequence.jsx", fn: "ppb_exportSequence" },
1003
+ "export-fcp-xml": { file: "export-fcp-xml.jsx", fn: "ppb_exportFcpXml" },
1004
+ "export-aaf": { file: "export-aaf.jsx", fn: "ppb_exportAaf" },
1005
+ "export-omf": { file: "export-omf.jsx", fn: "ppb_exportOmf" },
1006
+ "add-to-render-queue": { file: "add-to-render-queue.jsx", fn: "ppb_addToRenderQueue" },
1007
+ "create-subclip": { file: "create-subclip.jsx", fn: "ppb_createSubclip" },
1008
+ "encode-project-item": { file: "encode-project-item.jsx", fn: "ppb_encodeProjectItem" },
1009
+ "encode-file": { file: "encode-file.jsx", fn: "ppb_encodeFile" },
1010
+ "manage-proxies": { file: "manage-proxies.jsx", fn: "ppb_manageProxies" },
1011
+ "open-in-source": { file: "open-in-source.jsx", fn: "ppb_openInSource" },
1012
+ "close-source-monitor": { file: "close-source-monitor.jsx", fn: "ppb_closeSourceMonitor" },
1013
+ "close-all-source-clips": { file: "close-all-source-clips.jsx", fn: "ppb_closeAllSourceClips" },
1014
+ "set-source-in-out": { file: "set-source-in-out.jsx", fn: "ppb_setSourceInOut" },
1015
+ "insert-from-source": { file: "insert-from-source.jsx", fn: "ppb_insertFromSource" },
1016
+ "overwrite-from-source": { file: "overwrite-from-source.jsx", fn: "ppb_overwriteFromSource" },
1017
+ "add-text-overlay": { file: "add-text-overlay.jsx", fn: "ppb_addTextOverlay" },
1018
+ "import-mogrt": { file: "import-mogrt.jsx", fn: "ppb_importMogrt" },
1019
+ "import-mogrt-from-library": { file: "import-mogrt-from-library.jsx", fn: "ppb_importMogrtFromLibrary" },
1020
+ "get-mogrt-component": { file: "get-mogrt-component.jsx", fn: "ppb_getMogrtComponent" },
1021
+ "create-caption-track": { file: "create-caption-track.jsx", fn: "ppb_createCaptionTrack" },
1022
+ "replace-clip-media": { file: "replace-clip-media.jsx", fn: "ppb_replaceClipMedia" },
1023
+ "debug-qe-inspect": { file: "debug-qe-inspect.jsx", fn: "ppb_debugQeInspect" },
1024
+ "debug-qe-try-mutate": { file: "debug-qe-try-mutate.jsx", fn: "ppb_debugQeTryMutate" },
1025
+ "get-premiere-state": { file: "get-premiere-state.jsx", fn: "ppb_getPremiereState" },
1026
+ "inspect-dom-object": { file: "inspect-dom-object.jsx", fn: "ppb_inspectDomObject" },
1027
+ "get-open-projects": { file: "get-open-projects.jsx", fn: "ppb_getOpenProjects" },
1028
+ "set-active-project": { file: "set-active-project.jsx", fn: "ppb_setActiveProject" },
1029
+ "move-playhead": { file: "move-playhead.jsx", fn: "ppb_movePlayhead" },
1030
+ "get-work-area": { file: "get-work-area.jsx", fn: "ppb_getWorkArea" },
1031
+ "set-work-area": { file: "set-work-area.jsx", fn: "ppb_setWorkArea" },
1032
+ "get-sequence-in-out": { file: "get-sequence-in-out.jsx", fn: "ppb_getSequenceInOut" },
1033
+ "set-sequence-in-out": { file: "set-sequence-in-out.jsx", fn: "ppb_setSequenceInOut" },
1034
+ "is-work-area-enabled": { file: "is-work-area-enabled.jsx", fn: "ppb_isWorkAreaEnabled" },
1035
+ "get-export-file-extension": { file: "get-export-file-extension.jsx", fn: "ppb_getExportFileExtension" },
1036
+ "get-workspaces": { file: "get-workspaces.jsx", fn: "ppb_getWorkspaces" },
1037
+ "set-workspace": { file: "set-workspace.jsx", fn: "ppb_setWorkspace" },
1038
+ "play-timeline": { file: "play-timeline.jsx", fn: "ppb_playTimeline" },
1039
+ "stop-playback": { file: "stop-playback.jsx", fn: "ppb_stopPlayback" },
1040
+ "play-source-monitor": { file: "play-source-monitor.jsx", fn: "ppb_playSourceMonitor" },
1041
+ "get-source-monitor-position": { file: "get-source-monitor-position.jsx", fn: "ppb_getSourceMonitorPosition" },
1042
+ "get-version-info": { file: "get-version-info.jsx", fn: "ppb_getVersionInfo" },
1043
+ "get-bin-contents": { file: "get-bin-contents.jsx", fn: "ppb_getBinContents" },
1044
+ "get-project-item-info": { file: "get-project-item-info.jsx", fn: "ppb_getProjectItemInfo" },
1045
+ "get-timeline-gaps": { file: "get-timeline-gaps.jsx", fn: "ppb_getTimelineGaps" },
1046
+ "get-offline-media": { file: "get-offline-media.jsx", fn: "ppb_getOfflineMedia" },
1047
+ "get-used-media-report": { file: "get-used-media-report.jsx", fn: "ppb_getUsedMediaReport" },
1048
+ "get-all-project-paths": { file: "get-all-project-paths.jsx", fn: "ppb_getAllProjectPaths" },
1049
+ "get-unused-media": { file: "get-unused-media.jsx", fn: "ppb_getUnusedMedia" },
1050
+ "get-duplicate-media": { file: "get-duplicate-media.jsx", fn: "ppb_getDuplicateMedia" },
1051
+ "get-clip-links": { file: "get-clip-links.jsx", fn: "ppb_getClipLinks" },
1052
+ "get-insertion-bin": { file: "get-insertion-bin.jsx", fn: "ppb_getInsertionBin" },
1053
+ "get-project-panel-metadata": { file: "get-project-panel-metadata.jsx", fn: "ppb_getProjectPanelMetadata" },
1054
+ "list-available-effects": { file: "list-available-effects.jsx", fn: "ppb_listAvailableEffects" },
1055
+ "list-available-audio-effects": { file: "list-available-audio-effects.jsx", fn: "ppb_listAvailableAudioEffects" },
1056
+ "list-available-transitions": { file: "list-available-transitions.jsx", fn: "ppb_listAvailableTransitions" },
1057
+ "list-available-audio-transitions": { file: "list-available-audio-transitions.jsx", fn: "ppb_listAvailableAudioTransitions" },
1058
+ "list-markers": { file: "list-markers.jsx", fn: "ppb_listMarkers" },
1059
+ "get-clip-markers": { file: "get-clip-markers.jsx", fn: "ppb_getClipMarkers" },
1060
+ "get-sequence-markers-by-type": { file: "get-sequence-markers-by-type.jsx", fn: "ppb_getSequenceMarkersByType" },
1061
+ "get-item-metadata": { file: "get-item-metadata.jsx", fn: "ppb_getItemMetadata" },
1062
+ "get-color-label": { file: "get-color-label.jsx", fn: "ppb_getColorLabel" },
1063
+ "get-footage-interpretation": { file: "get-footage-interpretation.jsx", fn: "ppb_getFootageInterpretation" },
1064
+ "get-xmp-metadata": { file: "get-xmp-metadata.jsx", fn: "ppb_getXmpMetadata" },
1065
+ "get-color-space": { file: "get-color-space.jsx", fn: "ppb_getColorSpace" },
1066
+ "get-render-queue-status": { file: "get-render-queue-status.jsx", fn: "ppb_getRenderQueueStatus" },
1067
+ "get-clip-at-position": { file: "get-clip-at-position.jsx", fn: "ppb_getClipAtPosition" },
1068
+ "get-clip-at-playhead": { file: "get-clip-at-playhead.jsx", fn: "ppb_getClipAtPlayhead" },
1069
+ "get-next-edit-point": { file: "get-next-edit-point.jsx", fn: "ppb_getNextEditPoint" },
1070
+ "get-sequence-count": { file: "get-sequence-count.jsx", fn: "ppb_getSequenceCount" },
1071
+ "get-total-clip-count": { file: "get-total-clip-count.jsx", fn: "ppb_getTotalClipCount" },
1072
+ "get-target-tracks": { file: "get-target-tracks.jsx", fn: "ppb_getTargetTracks" },
1073
+ "get-track-info": { file: "get-track-info.jsx", fn: "ppb_getTrackInfo" },
1074
+ "get-encoder-presets": { file: "get-encoder-presets.jsx", fn: "ppb_getEncoderPresets" },
1075
+ "get-qe-clip-info": { file: "get-qe-clip-info.jsx", fn: "ppb_getQeClipInfo" },
1076
+ "get-source-monitor-info": { file: "get-source-monitor-info.jsx", fn: "ppb_getSourceMonitorInfo" },
1077
+ "get-clip-adjustment-layer": { file: "get-clip-adjustment-layer.jsx", fn: "ppb_getClipAdjustmentLayer" },
1078
+ "add-marker": { file: "add-marker.jsx", fn: "ppb_addMarker" },
1079
+ "update-marker": { file: "update-marker.jsx", fn: "ppb_updateMarker" },
1080
+ "delete-marker": { file: "delete-marker.jsx", fn: "ppb_deleteMarker" },
1081
+ "add-marker-to-project-item": { file: "add-marker-to-project-item.jsx", fn: "ppb_addMarkerToProjectItem" },
1082
+ "redo": { file: "redo.jsx", fn: "ppb_redo" },
1083
+ "undo": { file: "undo.jsx", fn: "ppb_undo" },
1084
+ "move-playhead-to-edit": { file: "move-playhead-to-edit.jsx", fn: "ppb_movePlayheadToEdit" },
1085
+ "set-poster-frame": { file: "set-poster-frame.jsx", fn: "ppb_setPosterFrame" },
1086
+ "select-project-item": { file: "select-project-item.jsx", fn: "ppb_selectProjectItem" },
1087
+ "select-clips-by-name": { file: "select-clips-by-name.jsx", fn: "ppb_selectClipsByName" },
1088
+ "select-all-clips": { file: "select-all-clips.jsx", fn: "ppb_selectAllClips" },
1089
+ "deselect-all-clips": { file: "deselect-all-clips.jsx", fn: "ppb_deselectAllClips" },
1090
+ "select-clips-in-range": { file: "select-clips-in-range.jsx", fn: "ppb_selectClipsInRange" },
1091
+ "select-clips-by-color": { file: "select-clips-by-color.jsx", fn: "ppb_selectClipsByColor" },
1092
+ "invert-selection": { file: "invert-selection.jsx", fn: "ppb_invertSelection" },
1093
+ "select-disabled-clips": { file: "select-disabled-clips.jsx", fn: "ppb_selectDisabledClips" },
1094
+ "set-clip-selection": { file: "set-clip-selection.jsx", fn: "ppb_setClipSelection" },
1095
+ "add-track": { file: "add-track.jsx", fn: "ppb_addTrack" },
1096
+ "lock-track": { file: "lock-track.jsx", fn: "ppb_lockTrack" },
1097
+ "set-track-visibility": { file: "set-track-visibility.jsx", fn: "ppb_setTrackVisibility" },
1098
+ "set-track-mute": { file: "set-track-mute.jsx", fn: "ppb_setTrackMute" },
1099
+ "rename-track": { file: "rename-track.jsx", fn: "ppb_renameTrack" },
1100
+ "set-target-track": { file: "set-target-track.jsx", fn: "ppb_setTargetTrack" },
1101
+ "set-all-tracks-targeted": { file: "set-all-tracks-targeted.jsx", fn: "ppb_setAllTracksTargeted" },
1102
+ "set-clip-position": { file: "set-clip-position.jsx", fn: "ppb_setClipPosition" },
1103
+ "set-clip-scale": { file: "set-clip-scale.jsx", fn: "ppb_setClipScale" },
1104
+ "set-clip-rotation": { file: "set-clip-rotation.jsx", fn: "ppb_setClipRotation" },
1105
+ "set-clip-anchor-point": { file: "set-clip-anchor-point.jsx", fn: "ppb_setClipAnchorPoint" },
1106
+ "set-clip-opacity": { file: "set-clip-opacity.jsx", fn: "ppb_setClipOpacity" },
1107
+ "set-uniform-scale": { file: "set-uniform-scale.jsx", fn: "ppb_setUniformScale" },
1108
+ "set-scale-width-height": { file: "set-scale-width-height.jsx", fn: "ppb_setScaleWidthHeight" },
1109
+ "set-anti-alias-quality": { file: "set-anti-alias-quality.jsx", fn: "ppb_setAntiAliasQuality" },
1110
+ "set-blend-mode": { file: "set-blend-mode.jsx", fn: "ppb_setBlendMode" },
1111
+ "apply-effect": { file: "apply-effect.jsx", fn: "ppb_applyEffect" },
1112
+ "apply-audio-effect": { file: "apply-audio-effect.jsx", fn: "ppb_applyAudioEffect" },
1113
+ "remove-effect": { file: "remove-effect.jsx", fn: "ppb_removeEffect" },
1114
+ "remove-effect-by-name": { file: "remove-effect-by-name.jsx", fn: "ppb_removeEffectByName" },
1115
+ "remove-all-effects": { file: "remove-all-effects.jsx", fn: "ppb_removeAllEffects" },
1116
+ "color-correct": { file: "color-correct.jsx", fn: "ppb_colorCorrect" },
1117
+ "apply-lut": { file: "apply-lut.jsx", fn: "ppb_applyLut" },
1118
+ "stabilize-clip": { file: "stabilize-clip.jsx", fn: "ppb_stabilizeClip" },
1119
+ "copy-effects-between-clips": { file: "copy-effects-between-clips.jsx", fn: "ppb_copyEffectsBetweenClips" },
1120
+ "copy-effect-values": { file: "copy-effect-values.jsx", fn: "ppb_copyEffectValues" },
1121
+ "batch-apply-effect": { file: "batch-apply-effect.jsx", fn: "ppb_batchApplyEffect" },
1122
+ "add-to-timeline": { file: "add-to-timeline.jsx", fn: "ppb_addToTimeline" },
1123
+ "remove-from-timeline": { file: "remove-from-timeline.jsx", fn: "ppb_removeFromTimeline" },
1124
+ "move-clip": { file: "move-clip.jsx", fn: "ppb_moveClip" },
1125
+ "trim-clip": { file: "trim-clip.jsx", fn: "ppb_trimClip" },
1126
+ "split-clip": { file: "split-clip.jsx", fn: "ppb_splitClip" },
1127
+ "duplicate-clip": { file: "duplicate-clip.jsx", fn: "ppb_duplicateClip" },
1128
+ "replace-clip": { file: "replace-clip.jsx", fn: "ppb_replaceClip" },
1129
+ "set-clip-speed": { file: "set-clip-speed.jsx", fn: "ppb_setClipSpeed" },
1130
+ "get-clip-speed": { file: "get-clip-speed.jsx", fn: "ppb_getClipSpeed" },
1131
+ "import-media": { file: "import-media.jsx", fn: "ppb_importMedia" },
1132
+ "import-folder": { file: "import-folder.jsx", fn: "ppb_importFolder" },
1133
+ "import-image-sequence": { file: "import-image-sequence.jsx", fn: "ppb_importImageSequence" },
1134
+ "create-bin": { file: "create-bin.jsx", fn: "ppb_createBin" },
1135
+ "rename-bin": { file: "rename-bin.jsx", fn: "ppb_renameBin" },
1136
+ "move-items-to-bin": { file: "move-items-to-bin.jsx", fn: "ppb_moveItemsToBin" },
1137
+ "relink-media": { file: "relink-media.jsx", fn: "ppb_relinkMedia" },
1138
+ "refresh-media": { file: "refresh-media.jsx", fn: "ppb_refreshMedia" },
1139
+ "set-item-offline": { file: "set-item-offline.jsx", fn: "ppb_setItemOffline" },
1140
+ "detach-proxy": { file: "detach-proxy.jsx", fn: "ppb_detachProxy" },
1141
+ "set-override-frame-rate": { file: "set-override-frame-rate.jsx", fn: "ppb_setOverrideFrameRate" },
1142
+ "set-override-pixel-aspect-ratio": { file: "set-override-pixel-aspect-ratio.jsx", fn: "ppb_setOverridePixelAspectRatio" },
1143
+ "set-scale-to-frame-size": { file: "set-scale-to-frame-size.jsx", fn: "ppb_setScaleToFrameSize" },
1144
+ "set-item-start-time": { file: "set-item-start-time.jsx", fn: "ppb_setItemStartTime" },
1145
+ "rename-project-item": { file: "rename-project-item.jsx", fn: "ppb_renameProjectItem" }
1146
+ };
1147
+
1148
+ // Files already evalFile'd this panel session. Deliberately keyed on this
1149
+ // object — which resets every time index.jsx re-evaluates (i.e. on every
1150
+ // panel load/reload) — and NOT on `typeof ppb_xxx === "function"`: if the
1151
+ // ExtendScript engine outlives a panel reload, a stale function from the
1152
+ // previous session would otherwise mask a newer command file on disk and
1153
+ // break the edit-file → reload-panel dev loop.
1154
+ var PPB_LOADED = {};
1155
+
1156
+ // Single entry point called by the panel (main.js) for every command:
1157
+ // ppb_dispatch('<command-name>', '<argsJson>')
1158
+ // Loads host/commands/<file> on first use, then calls the command function
1159
+ // with argsJson. The plugin's root directory comes from args.pluginDir,
1160
+ // which main.js injects into every command's args ($.fileName doesn't
1161
+ // resolve reliably for a manifest-loaded .jsx, so the panel supplies it).
1162
+ function ppb_dispatch(commandName, argsJson) {
1163
+ try {
1164
+ var entry = PPB_COMMANDS[commandName];
1165
+ if (!entry) {
1166
+ return JSON.stringify({ ok: false, error: "unknown command: " + commandName });
1167
+ }
1168
+
1169
+ if (!PPB_LOADED[entry.file]) {
1170
+ var pluginDir = null;
1171
+ try {
1172
+ pluginDir = JSON.parse(argsJson).pluginDir;
1173
+ } catch (e) {
1174
+ return JSON.stringify({ ok: false, error: "invalid args JSON: " + e.toString() });
1175
+ }
1176
+ if (!pluginDir || typeof pluginDir !== "string") {
1177
+ return JSON.stringify({ ok: false, error: "pluginDir was not provided by the panel" });
1178
+ }
1179
+
1180
+ var commandFile = new File(pluginDir + "/host/commands/" + entry.file);
1181
+ if (!commandFile.exists) {
1182
+ return JSON.stringify({ ok: false, error: "command file not found: " + commandFile.fsName });
1183
+ }
1184
+
1185
+ // Drop any stale copy from a previous panel session first, so the
1186
+ // eval() lookup below can only resolve the freshly-loaded function
1187
+ // (never a leftover global masking a broken/edited file on disk).
1188
+ try {
1189
+ delete $.global[entry.fn];
1190
+ } catch (e) {
1191
+ // best-effort
1192
+ }
1193
+
1194
+ try {
1195
+ $.evalFile(commandFile.fsName);
1196
+ } catch (e) {
1197
+ return JSON.stringify({ ok: false, error: "failed to load " + entry.file + ": " + e.toString() });
1198
+ }
1199
+
1200
+ // $.evalFile evaluates the file in the CALLING scope (confirmed
1201
+ // live 2026-07-17): its function declarations land in this
1202
+ // function's local scope, NOT on $.global. Resolve the command's
1203
+ // entry point from the local scope and publish it globally — the
1204
+ // closure keeps the file's sibling helper functions reachable from
1205
+ // it, and each file's helpers stay isolated in their own load
1206
+ // scope. Do NOT trust a pre-existing $.global[entry.fn] here: if
1207
+ // the ExtendScript engine outlives a panel reload, that global is
1208
+ // a stale copy from an earlier session masking the file on disk.
1209
+ var loadedFn = null;
1210
+ try {
1211
+ loadedFn = eval(entry.fn);
1212
+ } catch (e) {
1213
+ loadedFn = null;
1214
+ }
1215
+ if (typeof loadedFn !== "function") {
1216
+ return JSON.stringify({ ok: false, error: entry.file + " loaded but did not define " + entry.fn + "()" });
1217
+ }
1218
+ $.global[entry.fn] = loadedFn;
1219
+
1220
+ PPB_LOADED[entry.file] = true;
1221
+ }
1222
+
1223
+ return $.global[entry.fn](argsJson);
1224
+ } catch (e) {
1225
+ return JSON.stringify({ ok: false, error: e.toString() });
1226
+ }
1227
+ }