はじめに

After Effectsで作業していると、コンポジションをコピーしたいことがよくあります。

Ctrl + Dで複製できますが、ネストされたプリコンプは参照のままなので、コピー先で中身を変更すると元のコンポジションにも影響してしまいます。

完全に独立したコピーを簡単に生成したいので、タイムライン上のコンポジションをネストを外した状態で複製するスクリプトを作りました。

CompDuplicator.gif

ダウンロード

https://github.com/toritake-tech/ae-comp-duplicator/releases/tag/v1.3.0

概要

機能: - タイムライン上でコンポジションレイヤーを選択 - ボタンクリックで完全独立コピーを作成 - Ctrl+Dと同じようにタイムラインに追加される - 連番命名(copy, copy-2, copy-3...)

技術スタック

コード

全体構造

(function(thisObj) {
    var SCRIPT_NAME = "Comp Duplicator";
    var SCRIPT_VERSION = "1.3.0";

    // ユーティリティ関数群
    function getNextCopyNumber(baseName) { ... }
    function generateCopyName(originalName) { ... }
    function collectNestedComps(comp, collected) { ... }
    function getCompDepth(comp, allComps, cache) { ... }
    function sortCompsByDepth(comps, allCompsMap) { ... }

    // メイン処理
    function duplicateCompletely(parentComp, selectedLayer) { ... }

    // UI構築
    function buildUI(thisObj) { ... }

    buildUI(thisObj);
})(this);

ネストされたコンポジションの収集

  1. 全てのネストされたコンポジションを再帰的に収集
  2. 依存関係の順序で複製(深いものから)
  3. 参照を新しいコンポジションに差し替え
function collectNestedComps(comp, collected) {
    if (!collected) collected = {};
    if (collected[comp.id]) return collected;  // 循環参照防止

    collected[comp.id] = comp;

    // 全レイヤーを走査
    for (var i = 1; i <= comp.numLayers; i++) {
        var layer = comp.layer(i);
        // ソースがコンポジションなら再帰的に収集
        if (layer.source && layer.source instanceof CompItem) {
            collectNestedComps(layer.source, collected);
        }
    }

    return collected;
}

依存関係の解決:深さ計算

コンポジションAがBを含み、BがCを含む場合、Cから先に複製しないとBの参照が壊れてしまいます。

function getCompDepth(comp, allComps, cache) {
    if (!cache) cache = {};
    if (cache[comp.id] !== undefined) return cache[comp.id];

    var maxChildDepth = -1;

    for (var i = 1; i <= comp.numLayers; i++) {
        var layer = comp.layer(i);
        if (layer.source && layer.source instanceof CompItem) {
            if (allComps[layer.source.id]) {
                var childDepth = getCompDepth(layer.source, allComps, cache);
                if (childDepth > maxChildDepth) {
                    maxChildDepth = childDepth;
                }
            }
        }
    }

    cache[comp.id] = maxChildDepth + 1;
    return cache[comp.id];
}

深さの浅い順にソートして複製すれば、参照先が必ず先に存在します。

参照の差し替え

複製後、各レイヤーのソースを新しいコンポジションに置き換えます:

for (var origId in duplicateMap) {
    var dupComp = duplicateMap[origId];

    for (var j = 1; j <= dupComp.numLayers; j++) {
        var layer = dupComp.layer(j);

        if (layer.source && layer.source instanceof CompItem) {
            // 元のコンポジションIDから新しいコンポジションを取得
            for (var checkId in nestedCompsMap) {
                if (layer.source.id === nestedCompsMap[checkId].id) {
                    if (duplicateMap[checkId]) {
                        layer.replaceSource(duplicateMap[checkId], false);
                    }
                    break;
                }
            }
        }
    }
}

layer.replaceSource(newSource, false)で参照を差し替えます。

連番命名

同名のコピーが既にある場合、自動で連番を付けます:

function getNextCopyNumber(baseName) {
    var count = 0;
    var copyPattern = baseName + " (copy";

    // プロジェクト内の全アイテムを走査
    for (var i = 1; i <= app.project.numItems; i++) {
        var item = app.project.item(i);
        if (item instanceof CompItem) {
            var name = item.name;
            if (name === baseName + " (copy)" || 
                name.indexOf(copyPattern + "-") === 0) {
                count++;
            }
        }
    }

    return count + 1;
}

function generateCopyName(originalName) {
    // 既存の (copy) や (copy-N) を除去してベース名を取得
    var baseName = originalName.replace(/ \(copy(-\d+)?\)$/, "");
    var nextNum = getNextCopyNumber(baseName);

    if (nextNum === 1) {
        return baseName + " (copy)";
    } else {
        return baseName + " (copy-" + nextNum + ")";
    }
}

MyCompMyComp (copy)MyComp (copy-2)MyComp (copy-3)

タイムラインへの追加

Ctrl+Dと同じように、選択レイヤーの下にコピーを追加します:

// 親コンポジションのタイムラインにレイヤーとして追加
var newLayer = parentComp.layers.add(result);

// 元のレイヤーの位置・タイミングをコピー
newLayer.startTime = selectedLayer.startTime;
newLayer.inPoint = selectedLayer.inPoint;
newLayer.outPoint = selectedLayer.outPoint;

// 選択レイヤーのすぐ下に配置
newLayer.moveAfter(parentComp.layer(selectedLayerIndex));

// 新しいレイヤーを選択状態に
selectedLayer.selected = false;
newLayer.selected = true;

ScriptUI でパネル作成

function buildUI(thisObj) {
    // ScriptUI Panelとしてドッキング可能、または独立ウィンドウ
    var win = (thisObj instanceof Panel) 
        ? thisObj 
        : new Window("palette", SCRIPT_NAME, undefined, {resizeable: true});

    var mainGroup = win.add("group");
    mainGroup.orientation = "column";

    // ボタン
    var duplicateBtn = mainGroup.add("button", undefined, "コンポジションを複製");
    duplicateBtn.onClick = function() {
        // 処理実行
    };

    // ウィンドウ表示
    if (win instanceof Window) {
        win.center();
        win.show();
    } else {
        win.layout.layout(true);
    }
}

インストール方法

  1. CompDuplicator.jsxをダウンロード
  2. 以下のフォルダにコピー: Windows: C:\Program Files\Adobe\Adobe After Effects 20XX\Support Files\Scripts\ScriptUI Panels\ Mac: /Applications/Adobe After Effects 20XX/Scripts/ScriptUI Panels/
  3. After Effectsを再起動
  4. WindowCompDuplicator.jsx
  5. GUIが出てきます

使い方

  1. 親コンポジションを開く
  2. タイムライン上でコンポジションレイヤーを選択
  3. パネルの「コンポジションを複製」ボタンをクリック
  4. 選択レイヤーの下に完全独立コピーが追加される

配布

https://github.com/toritake-tech/ae-comp-duplicator/releases/tag/v1.3.0