使用AI大模型辅助生成(我们俩真厉害)
一个简单的小脚本,功能:自定义阵列,快速给素材定位到对应单元格,支持多图层分别分配位置,三种素材处理方式,点击应用同步图层位置
建议在MotionToolsPro中使用,因为我只测试了这个,并且该脚本无法实时预览位置(有bug会崩溃不会修所以摆了qwq
如果未来有可以搭配使用的功能可能会整合进来
单个图层









// om_pv.jsx
// om_pv阵列快速定位工具 (常驻面板版,精简图层分配UI,按钮样式名称)
// ========== 可自定义参数(可通过齿轮菜单修改部分数值) ==========
var MAX_COLS = 20;
var MAX_ROWS = 20;
var DEFAULT_COLS = 3;
var DEFAULT_ROWS = 3;
var BTN_SIZE_MIN = 20;
var BTN_SIZE_MAX = 50;
var LAYER_BTN_MIN_W = 120;
var DEFAULT_SCALE = true;
var DEFAULT_STRETCH = false;
var DEFAULT_FILL_CLIP = false;
var DEFAULT_GAP_X = 0;
var DEFAULT_GAP_Y = 0;
var GAP_STEP = 5;
var CN_CHAR_W = 11;
var EN_CHAR_W = 7;
// =================================
(function() {
var comp = app.project.activeItem;
if (!comp || !(comp instanceof CompItem)) { alert("请先激活合成窗口", "BAKA"); return; }
var layers = comp.selectedLayers;
if (!layers.length) { alert("请先在时间轴中选择至少一个图层", "BAKA"); return; }
// 筛选有效图层
var validLayers = [];
var validLayerNames = [];
for (var i = 0; i < layers.length; i++) {
var l = layers[i];
if (l instanceof AVLayer || l instanceof ShapeLayer || l instanceof TextLayer) {
if (l.name.indexOf("_遮罩") < 0) {
validLayers.push(l);
validLayerNames.push(l.name || ("图层 " + (i + 1)));
}
}
}
if (!validLayers.length) { alert("请选择至少一个可视图层(不含音频或遮罩)", "BAKA"); return; }
function src(l) { return (l.source || l); }
function tw(s) { for (var i = 0, w = 0; i < s.length; i++) w += (s.charCodeAt(i) > 127) ? CN_CHAR_W : EN_CHAR_W; return w; }
// ===== 齿轮图标 =====
function base64Decode(input) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var output = [];
var i = 0;
while (i < input.length) {
var enc1 = chars.indexOf(input.charAt(i++));
var enc2 = chars.indexOf(input.charAt(i++));
var enc3 = chars.indexOf(input.charAt(i++));
var enc4 = chars.indexOf(input.charAt(i++));
var chr1 = (enc1 << 2) | (enc2 >> 4);
var chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
var chr3 = ((enc3 & 3) << 6) | enc4;
output.push(String.fromCharCode(chr1));
if (enc3 != 64) output.push(String.fromCharCode(chr2));
if (enc4 != 64) output.push(String.fromCharCode(chr3));
}
return output.join('');
}
function createTempPng(base64, filename) {
var f = new File(Folder.temp.fsName + "/" + filename);
if (f && f.open("w")) {
f.encoding = "binary";
f.write(base64Decode(base64));
f.close();
}
return f;
}
var gearBase64 = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAEMklEQVRIib1WTUgjWRCudeemiK4N4iy6GjYHESHYZC8yvqcEVvxBUEQvUcQQzOYayDALe3IGc1ToGQysoBdJBvUQVi9BXoSAkDQEg+QQiBsRvXQiO8bAHpYsX6c7ZESduewWNP3yut6rqq/qq8o3jDH6Smk11G6/9kAsFqMXWAghnlTinPuIyCNJkgW/NU2DgY9E9EEIkXrmXHWBCCqVyqMPY0z2+XwVTdMqptzf31disViFMbb51DnjrH7/iy9E6Z6cnNQXGxsb+ttms9HQ0BBZrVY35zwghMg9d0HNAOe8GVAQEaCICiEAg9zT00Pn5+e0v78fxH46nQ7DQH9/P2WzWZlzDsjcRo4+CiHURw0Q0ZrX6/W0t7dTKpWCd6okSXJbWxudnp7iew5GOee5QqFgsVqt2PNLkhSem5ujpqYmOj4+9nPOXUKI32u3AivG2NutrS0d5cvLy9ob2OM9MzMDzFsNbP3hcLimg5zgMXUZY0nG2A9mDlCmrZIkFXd2dvSyCgQCqt1ul0dGRqhUKlEoFELluIhIMmCIEtH09PS0B1GYOpC6OwJE9BprGPB7vd618fFxWlhYwGXdxmWzRs03S5L0xoRBVVWKRqMwgpzIhg7Wfgg8Hxsbw953MICQw8lkUg/R5XIhxLd1pfYecACCeslkMiZsw4aeDWV7eHioa6G0UeJmDoahbOJpfPSZHICg7mEceuvr67oenIJzjLFmXA4dCByCY2YOvu3u7v6zXC5fHRwcfD8wMPByYmKCwuHwj8B7ZWVFBiwejydXLBZHyuXym0wm809LS8srh8NB8Xi8r1gs/uX1elE9wB75+AD8iejvfD5PDVRtFSirQDweJ5Sl0RZ0DpTLZajcoi0IIYBt6ubmRvcOXIAjcKJQKCA3qhDiFyHEp894wDlfBskGBwd1RU3TwE714uJC7uvr0y/hnNuIKA8yd3R06IfT6bRuHJXU2dlJDocDxHtvRPDJhGhYkqS9zc3Nl11dXbS6ukrX19fviGivVCq5p6amyGKxtF5dXa00NDT4R0dHX83Pz9PZ2Rnt7u6C7b8lEok2i8UiO51OamxstCcSiRYi+gMQ/edVBIjUXC4329vbS7e3eqsPcs4dRIQnryjKu1Ao9BQPfuac/2Ss9cPIWSKRwFo1DQRDoZBOtOXlZVTCHrCUZfkzJiuK8pDJ4Tomr5ltRydX1eD/04vMifYr5xzl5gEM29vbesmhm6K/AB5FUdCS0WPc6FNHR0d634IOvqMLRyIRMiZd3pxo9e36taIoeRMGIUSUc54sFAoyDmNOcM5n7Xa7BVzJZrPYC2iaFjWMgzvBJ+eBQY7Ag4FU48Li4qL77u7ObcwBkwMgFhL68NyjA+cxCUYiETcYvbS0VPt+cnKCCODts+PyiwYQLiaU0+nEv4pqd6z+s0CVoOc8L0T0L32CI0/szYESAAAAAElFTkSuQmCC";
// ===== 设置弹窗 =====
function showSettings() {
var sd = new Window("dialog", "设置");
sd.orientation = "column"; sd.alignChildren = "center"; sd.spacing = 8; sd.margins = 12;
function row(label, val) {
var g = sd.add("group"); g.orientation = "row"; g.alignChildren = "center"; g.spacing = 6;
g.add("statictext", undefined, label).preferredSize = [100, 18];
var inp = g.add("edittext", undefined, val.toString()); inp.characters = 6; inp.active = true; inp.justify = "center";
return inp;
}
var maxC = row("最大列数", MAX_COLS);
var maxR = row("最大行数", MAX_ROWS);
var defC = row("默认列数", DEFAULT_COLS);
var defR = row("默认行数", DEFAULT_ROWS);
var gapStep = row("间距步长", GAP_STEP);
var bg = sd.add("group"); bg.orientation = "row"; bg.alignChildren = "center"; bg.spacing = 10;
bg.add("button", undefined, "取消").onClick = function() { sd.close(); };
bg.add("button", undefined, "保存").onClick = function() {
MAX_COLS = parseInt(maxC.text) || 20;
MAX_ROWS = parseInt(maxR.text) || 20;
DEFAULT_COLS = parseInt(defC.text) || 3;
DEFAULT_ROWS = parseInt(defR.text) || 3;
GAP_STEP = parseInt(gapStep.text) || 5;
sd.close();
};
sd.center(); sd.show();
}
// ===== 构建非模态面板 =====
var win = new Window("palette", "om_pv", undefined, {resizeable: true});
win.orientation = "column"; win.alignChildren = "center"; win.spacing = 8; win.margins = 12;
win.add("statictext", undefined, "阵列设置");
var SPIN_BTN_SIZE = 22;
function makeSpinControl(parent, label, value) {
var g = parent.add("group"); g.orientation = "row"; g.alignChildren = "center"; g.spacing = 2;
g.add("statictext", undefined, label).preferredSize = [36, 18];
var btnDec = g.add("button", undefined, "◀"); btnDec.preferredSize = [SPIN_BTN_SIZE, SPIN_BTN_SIZE];
var edit = g.add("edittext", undefined, value.toString()); edit.characters = 3; edit.active = true; edit.justify = "center";
var btnInc = g.add("button", undefined, "▶"); btnInc.preferredSize = [SPIN_BTN_SIZE, SPIN_BTN_SIZE];
return { g: g, dec: btnDec, inc: btnInc, edit: edit };
}
var row1 = win.add("group"); row1.orientation = "row"; row1.alignChildren = "center"; row1.spacing = 15;
var ctrlW = makeSpinControl(row1, "宽:", DEFAULT_COLS);
var ctrlH = makeSpinControl(row1, "高:", DEFAULT_ROWS);
var row2 = win.add("group"); row2.orientation = "row"; row2.alignChildren = "center"; row2.spacing = 15;
var ctrlGX = makeSpinControl(row2, "间距X:", DEFAULT_GAP_X);
var ctrlGY = makeSpinControl(row2, "间距Y:", DEFAULT_GAP_Y);
var info = win.add("statictext", undefined, ""); info.alignment = "center"; info.preferredSize = [200, 18];
var preview = win.add("statictext", undefined, "", {multiline: true}); preview.alignment = "center"; preview.preferredSize = [200, 100];
// 图层分配区域 —— 使用按钮样式显示图层名称
win.add("statictext", undefined, "图层位置分配");
var layerAssignPanel = win.add("group"); layerAssignPanel.orientation = "column"; layerAssignPanel.alignChildren = "fill"; layerAssignPanel.spacing = 4;
var maxNameWidth = 0;
for (var i = 0; i < validLayerNames.length; i++) {
var w = tw(validLayerNames[i]);
if (w > maxNameWidth) maxNameWidth = w;
}
var layerNameWidth = Math.max(120, maxNameWidth + 10);
var slotNumberDropdowns = [];
for (var i = 0; i < validLayers.length; i++) {
(function(idx) {
var lr = layerAssignPanel.add("group"); lr.orientation = "row"; lr.alignChildren = "center"; lr.spacing = 6;
// 按钮样式显示图层名称,不可点击(或点击无动作)
var nameBtn = lr.add("button", undefined, validLayerNames[idx]);
nameBtn.preferredSize = [layerNameWidth, 22];
nameBtn.alignment = ["left", "center"];
// 可选:禁用按钮使其呈灰色不可点,但为了美观可以不禁用,只是不绑定事件
// nameBtn.enabled = false;
lr.add("statictext", undefined, "→ 位置:").preferredSize = [48, 18];
var ndd = lr.add("dropdownlist", undefined, []);
ndd.preferredSize = [60, 22];
ndd.add("item", "未分配");
slotNumberDropdowns.push(ndd);
})(i);
}
var optGrp = win.add("group"); optGrp.orientation = "row"; optGrp.alignChildren = "center"; optGrp.spacing = 10;
var doS = DEFAULT_SCALE, doT = DEFAULT_STRETCH, doF = DEFAULT_FILL_CLIP;
var scb = optGrp.add("checkbox", undefined, "缩放适配"); scb.value = doS; scb.enabled = !doF;
var tcb = optGrp.add("checkbox", undefined, "拉伸填充"); tcb.value = doT; tcb.enabled = doS && !doF;
var fcb = optGrp.add("checkbox", undefined, "填满裁剪"); fcb.value = doF;
var bottomGrp = win.add("group");
bottomGrp.orientation = "row";
bottomGrp.alignChildren = ["left", "center"];
bottomGrp.alignment = ["fill", "bottom"];
bottomGrp.spacing = 0;
var gearFile = createTempPng(gearBase64, "om_pv_gear.png");
var gearBtn = bottomGrp.add("iconbutton", undefined, gearFile, {style: "toolbutton"});
gearBtn.size = [26, 26];
gearBtn.onClick = function() { showSettings(); };
gearFile.remove();
var spacer = bottomGrp.add("statictext", undefined, "");
spacer.alignment = ["fill", "center"];
var applyBtn = bottomGrp.add("button", undefined, "应用");
applyBtn.preferredSize = [60, 26];
applyBtn.alignment = ["right", "center"];
// ===== 内部函数 =====
function read() {
var c = parseInt(ctrlW.edit.text) || 1, r = parseInt(ctrlH.edit.text) || 1;
if (c > MAX_COLS) { c = MAX_COLS; ctrlW.edit.text = c; }
if (r > MAX_ROWS) { r = MAX_ROWS; ctrlH.edit.text = r; }
return {c: Math.max(1, c), r: Math.max(1, r)};
}
function readGap() {
var gx = parseInt(ctrlGX.edit.text) || 0, gy = parseInt(ctrlGY.edit.text) || 0;
if (gx < 0) gx = 0; if (gy < 0) gy = 0;
return {gx: gx, gy: gy};
}
function setGap(gx, gy) { ctrlGX.edit.text = gx; ctrlGY.edit.text = gy; }
function updateNumberDropdowns() {
var v = read();
var total = v.c * v.r;
for (var i = 0; i < slotNumberDropdowns.length; i++) {
var ndd = slotNumberDropdowns[i];
var prevIdx = ndd.selection ? ndd.selection.index : 0;
ndd.removeAll();
ndd.add("item", "未分配");
for (var n = 1; n <= total; n++) ndd.add("item", n.toString());
if (prevIdx <= 0 || prevIdx > total) ndd.selection = (i + 1 <= total) ? i + 1 : 0;
else ndd.selection = prevIdx;
}
}
function upd() {
var v = read(), dg = (v.c * v.r).toString().length, t = "";
info.text = v.c + " × " + v.r + "(共 " + (v.c * v.r) + " 格)";
for (var i = 0; i < v.r; i++, t += "\n") for (var j = 0; j < v.c; j++) {
var n = (i * v.c + j + 1).toString(); while (n.length < dg) n = " " + n; t += "[" + n + "]";
}
preview.text = t;
updateNumberDropdowns();
win.layout.resize();
}
ctrlW.dec.onClick = function() { var v = read(); if (v.c > 1) { ctrlW.edit.text = v.c - 1; upd(); } };
ctrlW.inc.onClick = function() { var v = read(); if (v.c < MAX_COLS) { ctrlW.edit.text = v.c + 1; upd(); } };
ctrlH.dec.onClick = function() { var v = read(); if (v.r > 1) { ctrlH.edit.text = v.r - 1; upd(); } };
ctrlH.inc.onClick = function() { var v = read(); if (v.r < MAX_ROWS) { ctrlH.edit.text = v.r + 1; upd(); } };
ctrlGX.dec.onClick = function() { var g = readGap(); if (g.gx >= GAP_STEP) setGap(g.gx - GAP_STEP, g.gy); };
ctrlGX.inc.onClick = function() { var g = readGap(); setGap(g.gx + GAP_STEP, g.gy); };
ctrlGY.dec.onClick = function() { var g = readGap(); if (g.gy >= GAP_STEP) setGap(g.gx, g.gy - GAP_STEP); };
ctrlGY.inc.onClick = function() { var g = readGap(); setGap(g.gx, g.gy + GAP_STEP); };
ctrlW.edit.onChanging = ctrlH.edit.onChanging = upd;
upd();
scb.onClick = function() {
doS = scb.value;
tcb.enabled = doS && !doF;
if (!doS) { tcb.value = false; doT = false; } else doT = tcb.value;
};
tcb.onClick = function() { doT = tcb.value; };
fcb.onClick = function() {
doF = fcb.value;
if (doF) { scb.value = true; scb.enabled = false; tcb.value = false; tcb.enabled = false; doS = true; doT = false; }
else { scb.enabled = true; doS = scb.value; tcb.enabled = doS; doT = tcb.value; }
};
applyBtn.onClick = function() {
var v = read(), gap = readGap();
var cols = v.c, rows = v.r, gx = gap.gx, gy = gap.gy;
var totalGapX = gx * (cols - 1);
var totalGapY = gy * (rows - 1);
var cellW = (comp.width - totalGapX) / cols;
var cellH = (comp.height - totalGapY) / rows;
app.beginUndoGroup("om_pv");
for (var i = 0; i < slotNumberDropdowns.length; i++) {
var ndd = slotNumberDropdowns[i];
if (ndd.selection && ndd.selection.index > 0) {
var l = validLayers[i];
var num = parseInt(ndd.selection.text);
if (num >= 1 && num <= cols * rows) {
var col = (num - 1) % cols;
var row = Math.floor((num - 1) / cols);
var tx = (col + .5) * cellW + col * gx;
var ty = (row + .5) * cellH + row * gy;
try {
if (doS || doF) {
var s = src(l), sx = (cellW / s.width) * 100, sy = (cellH / s.height) * 100;
if (doF) { var sc = Math.max(sx, sy); l.scale.setValue([sc, sc]); }
else if (doT) l.scale.setValue([sx, sy]);
else { var sc = Math.min(sx, sy); l.scale.setValue([sc, sc]); }
} else l.scale.setValue([100, 100]);
l.position.setValue([tx, ty]);
} catch (e) {}
}
}
}
app.endUndoGroup();
app.beginUndoGroup("om_pv_mask");
for (var i = 0; i < validLayers.length; i++) validLayers[i].trackMatteType = TrackMatteType.NO_TRACK_MATTE;
for (var j = comp.numLayers; j >= 1; j--) {
var cl = comp.layer(j);
if (cl.name.indexOf("_遮罩") > 0) cl.remove();
}
if (doF) {
for (var i = 0; i < slotNumberDropdowns.length; i++) {
var ndd = slotNumberDropdowns[i];
if (ndd.selection && ndd.selection.index > 0) {
var l = validLayers[i];
var s = comp.layers.addSolid([1, 1, 1], l.name + "_遮罩", cellW, cellH, 1);
s.moveBefore(l); s.position.setValue(l.position.value); s.opacity.setValue(100);
l.trackMatteType = TrackMatteType.ALPHA;
}
}
}
app.endUndoGroup();
app.refresh();
};
win.onResizing = win.onResize = function() { win.layout.resize(); };
win.center();
win.show();
})(); 6/27:优化ui,添加间距参数,可自定义默认参数