(function duplicateCompDeep() {
var DEFAULT_SUFFIX = "_copy";
var suffix = DEFAULT_SUFFIX;
// ---------- Recursive duplication ----------
// original comp id -> { orig: CompItem, copy: CompItem }
var cache = {};
// old layer name -> new layer name (AE renames a layer when its source is
// swapped and the layer name was following the source name)
var layerNameMap = {};
function swapSource(layer, newSrc) {
var oldName = layer.name;
layer.replaceSource(newSrc, true);
if (layer.name !== oldName) layerNameMap[oldName] = layer.name;
}
function deepDuplicate(comp) {
if (cache[comp.id]) return cache[comp.id].copy;
var newComp = comp.duplicate();
newComp.name = comp.name + suffix;
cache[comp.id] = { orig: comp, copy: newComp };
for (var i = 1; i <= newComp.numLayers; i++) {
var layer = newComp.layer(i);
if (!(layer instanceof AVLayer)) continue; // lights, cameras...
if (layer.source instanceof CompItem) {
var newSrc = deepDuplicate(layer.source);
swapSource(layer, newSrc);
}
}
return newComp;
}
// ---------- Expression rewriting ----------
function walkProps(group, fn) {
for (var i = 1; i <= group.numProperties; i++) {
var p = group.property(i);
if (p.propertyType === PropertyType.PROPERTY) {
fn(p);
} else {
walkProps(p, fn);
}
}
}
// Rewrites, in every expression of the duplicated tree:
// comp("Old") -> comp("Old_suffix") for every duplicated comp
// layer("Old") -> layer("Old_suffix") for every layer that got renamed
// because its source was swapped
// Returns the number of expressions changed.
function fixExpressions() {
var nameMap = {};
for (var id in cache) {
if (!cache.hasOwnProperty(id)) continue;
nameMap[cache[id].orig.name] = cache[id].copy.name;
}
var reComp = /comp\(\s*(["'])(.*?)\1\s*\)/g;
var reLayer = /layer\(\s*(["'])(.*?)\1\s*\)/g;
var changed = 0;
for (var id2 in cache) {
if (!cache.hasOwnProperty(id2)) continue;
var newComp = cache[id2].copy;
for (var l = 1; l <= newComp.numLayers; l++) {
var layer = newComp.layer(l);
var wasLocked = layer.locked;
if (wasLocked) layer.locked = false; // locked layers refuse edits
walkProps(layer, function (prop) {
if (!prop.canSetExpression || prop.expression === "") return;
var src = prop.expression;
var out = src.replace(reComp, function (match, quote, name) {
if (nameMap.hasOwnProperty(name)) {
return 'comp(' + quote + nameMap[name] + quote + ')';
}
return match;
});
out = out.replace(reLayer, function (match, quote, name) {
if (layerNameMap.hasOwnProperty(name)) {
return 'layer(' + quote + layerNameMap[name] + quote + ')';
}
return match;
});
if (out !== src) {
try {
prop.expression = out;
changed++;
} catch (e) {
// leave the original expression untouched on failure
}
}
});
if (wasLocked) layer.locked = true;
}
}
return changed;
}
// ---------- Figure out what to duplicate ----------
var activeComp = (app.project.activeItem instanceof CompItem) ? app.project.activeItem : null;
// 1) Pre-comp layers selected in the Timeline
var precompLayers = [];
if (activeComp) {
var selLayers = activeComp.selectedLayers;
for (var i = 0; i < selLayers.length; i++) {
var l = selLayers[i];
if (l instanceof AVLayer && l.source instanceof CompItem) precompLayers.push(l);
}
}
// 2) Fallback: comps selected in the Project panel, then the active comp
var projectComps = [];
if (precompLayers.length === 0) {
var sel = app.project.selection;
for (var j = 0; j < sel.length; j++) {
if (sel[j] instanceof CompItem) projectComps.push(sel[j]);
}
if (projectComps.length === 0 && activeComp) projectComps.push(activeComp);
}
if (precompLayers.length === 0 && projectComps.length === 0) {
alert("Select a pre-comp layer in the Timeline (or open a comp) and run the script again.");
return;
}
var answer = prompt("Suffix for the duplicated comp names:", DEFAULT_SUFFIX, "Duplicate Comp Deep");
if (answer === null) return; // cancelled
if (answer !== "") suffix = answer;
// ---------- Run ----------
app.beginUndoGroup("Duplicate Comp Deep");
try {
var created = [];
if (precompLayers.length > 0) {
// Timeline mode: duplicate each selected layer's comp and swap the layer to it
for (var p = 0; p < precompLayers.length; p++) {
var lay = precompLayers[p];
var newComp = deepDuplicate(lay.source);
swapSource(lay, newComp);
created.push(newComp);
}
} else {
// Project mode: duplicate the comp itself
for (var t = 0; t < projectComps.length; t++) {
created.push(deepDuplicate(projectComps[t]));
}
for (var s = 1; s <= app.project.numItems; s++) app.project.item(s).selected = false;
for (var c = 0; c < created.length; c++) created[c].selected = true;
}
var exprCount = fixExpressions();
var total = 0;
for (var k in cache) if (cache.hasOwnProperty(k)) total++;
alert("Done.\n" + created.length + " comp(s) duplicated, " + total +
" composition(s) created in total (including nested pre-comps).\n" +
exprCount + " expression(s) re-linked to the new comps.");
} catch (err) {
alert("Duplicate Comp Deep - error:\n" + err.toString());
} finally {
app.endUndoGroup();
}
})();