Skip to main content
Inspiring
November 16, 2024
Question

自作したファイル整理スクリプトが不明のエラーを出すようになってしまった。

  • November 16, 2024
  • 1 reply
  • 277 views

かなり複雑な機能を実装しているフォルダー整理スクリプトなのですが、数時間前は正常に動作していたのですが、開発を進めるために再度実行しようとしたら原因が特定できないエラーに遭遇するようになりました。エラーは、「isSolidFolderが未定義です」という内容です。

/*  
ver4.3開発中:
-現在、ルートにあるフォルダーは、このスクリプトで生成したものであれば、中身が空でも残すようにしていますが、これについて、「ルートに空のフォルダーを残す」という確認ができるウィンドウを出現させ、
残す、の場合は現在の処理のまま、ユーザーが「残さない」を選んだ場合は、すべてのファイル整理プロセスが終わったあと、このスクリプトで生成したフォルダーであっても、空のフォルダーは削除するようにします。

-現在、ルートには、映像に使う素材としてIMAGE,AUDIO,VIDEO,IMAGE_SEQ,TEXT,illustrator,Photoshop,AfterEffects,PremierPro,3DDataを管理できるようにフォルダーがそれぞれ生成されている。
これによりプロジェクトのルートに、大量のフォルダーが生成されており、視認性が悪い。そのため、先程列挙したフォルダーを全て「02_FOOTAGE」というフォルダーの中に入るようにする機能を
同様にウィンドウで選択できるようにします。こちらは、「素材をフッテージフォルダーにまとめる」という項目で選択できるように開発中。


リリースメモ
-プロジェクト内にaepフォルダーがあった場合のファイル整理を実装
-環境設定での平面フォルダーに関わらず動作するようにしました。
-ラベルカラーをランダムにする機能を追加しました。
-Renderフォルダーへの振り分け機能を追加しました。
-フォルダー構成をふやし、ファイルタイプごとの振り分けを強化しました。
-「プロジェクト名.aep」フォルダー内の空のフォルダーを自動的に削除する機能を追加しました。
-ルートに余分なフォルダーがあった場合にOTHERフォルダーへ移動。
-「09_AfterEffects」>「プロジェクト名.aep」>の中でファイル整理を行うときに、既に「平面」や「Solid」を含む名前のフォルダーがあった場合それは「00_SOLID」とリネーム

注意
AfterEffects2025日本語版/英語版にて動作確認済です。

AfterEffects2023では動作しないバグを確認済。
->修正方法検討中...;;
*/


// フォルダ構造をグローバルに定義
var folderStructure = {
    solidFolder: "00_SOLID",
    categories: [
        "01_COMP",
        "02_IMAGE",
        "03_AUDIO",
        "04_VIDEO",
        "05_IMAGE_SEQ",
        "06_TEXT",
        "07_Illustrator",
        "08_Photoshop",
        "09_AfterEffects",
        "10_PremierePro",
        "11_3D_DATA",
        "12_EFFECT",
        "98_OTHER",
        "99_RENDER"
    ]
};

function main() {
    if (!app.project) {
        alert("プロジェクトが開かれていません。プロジェクトを開いてから実行してください。");
        return;
    }

    try {
        app.beginUndoGroup("Create and Organize Project Folders By Salis");

        var dummyComp = app.project.items.addComp("Dummy", 1920, 1080, 1.0, 10, 30);
        var dummySolid = dummyComp.layers.addSolid([1, 1, 1], "Dummy Solid", 1920, 1080, 1);
        var solidFolder = dummySolid.source.parentFolder;

        if (solidFolder) {
            solidFolder.name = folderStructure.solidFolder;
        }

        var solidSource = dummySolid.source;
        if (solidSource) {
            solidSource.remove();
        }
        dummyComp.remove();

        // メイン処理
        // ルートフォルダーには全てのフォルダーを作成
        var rootFolders = createFolderStructure(app.project.rootFolder, true);

        // ルートのアイテムがなくなるまで繰り返し整理
        var maxIterations = 100; // 無限ループ防止
        var iteration = 0;
        var hasUnorganizedItems = true;

        while (hasUnorganizedItems && iteration < maxIterations) {
            hasUnorganizedItems = false;

            // AEPフォルダーの移動と内部構造の作成
            moveAndOrganizeAepFolders(app.project.rootFolder, rootFolders);

            // ルートにある未整理アイテムをチェック
            for (var i = 1; i <= app.project.items.length; i++) {
                var item = app.project.items[i];
                if (item.parentFolder === app.project.rootFolder && !(item instanceof FolderItem)) {
                    hasUnorganizedItems = true;
                    break;
                }
            }

            // ルートの未分類フォルダーを移動
            moveUnorganizedFolders(app.project.rootFolder, rootFolders);

            // ルートのアイテムを整理
            organizeItems(app.project.rootFolder, rootFolders);

            iteration++;
        }

        // 空フォルダーの削除(ルートフォルダーの生成フォルダーは保護)
        removeEmptyFolders(app.project.rootFolder, true);

    } catch (error) {
        alert("エラーが発生しました:\n" + error.toString());
    } finally {
        app.endUndoGroup();
    }
}

// AEPフォルダーの移動と内部構造の作成を行う関数
function moveAndOrganizeAepFolders(rootFolder, rootFolders) {
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item instanceof FolderItem && item.parentFolder === rootFolder && isAepFolder(item.name)) {
            // AEPフォルダー内の構造を作成
            var aepFolders = createFolderStructure(item, false);

            // AEPフォルダー内のアイテムを整理
            organizeItems(item, aepFolders);

            // AEPフォルダー自体を AfterEffects フォルダーに移動
            item.parentFolder = rootFolders["09_AfterEffects"];
        }
    }
}

// アイテムを適切なフォルダーに整理する関数
function organizeItems(parentFolder, targetFolders) {
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item.parentFolder === parentFolder) {
            if (item instanceof FolderItem) {
                if (!isScriptGeneratedFolder(item.name) && !isAepFolder(item.name)) {
                    // 未分類フォルダーは OTHER に移動
                    item.parentFolder = targetFolders["98_OTHER"];
                }
            } else {
                // 通常のアイテムを整理
                var folderType = determineItemFolderType(item);
                if (folderType && targetFolders[folderType]) {
                    item.parentFolder = targetFolders[folderType];
                }
            }
        }
    }
}

// 未分類フォルダーを移動
function moveUnorganizedFolders(rootFolder, folders) {
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item instanceof FolderItem &&
            item.parentFolder === rootFolder &&
            !isScriptGeneratedFolder(item.name) &&
            !isAepFolder(item.name)) {
            item.parentFolder = folders["98_OTHER"];
        }
    }
}

// アイテムのフォルダータイプを判定する関数
function determineItemFolderType(item) {
    if (item instanceof CompItem) {
        return isRenderComp(item.name) ? "99_RENDER" : "01_COMP";
    } else if (item instanceof FootageItem) {
        if (item.mainSource instanceof SolidSource) {
            return folderStructure.solidFolder;
        } else if (item.mainSource instanceof FileSource) {
            var fileExtension = item.mainSource.file.name.split('.').pop().toLowerCase();

            if (item.mainSource.isStill === false &&
                (fileExtension === 'png' || fileExtension === 'jpg' || fileExtension === 'jpeg')) {
                return "05_IMAGE_SEQ";
            }

            var extensionMap = {
                'jpg': "02_IMAGE",
                'jpeg': "02_IMAGE",
                'png': "02_IMAGE",
                'gif': "02_IMAGE",
                'bmp': "02_IMAGE",
                'tiff': "02_IMAGE",
                'wav': "03_AUDIO",
                'mp3': "03_AUDIO",
                'aif': "03_AUDIO",
                'aac': "03_AUDIO",
                'ogg': "03_AUDIO",
                'mp4': "04_VIDEO",
                'mov': "04_VIDEO",
                'avi': "04_VIDEO",
                'mkv': "04_VIDEO",
                'flv': "04_VIDEO",
                'wmv': "04_VIDEO",
                'txt': "06_TEXT",
                'csv': "06_TEXT",
                'xml': "06_TEXT",
                'json': "06_TEXT",
                'ai': "07_Illustrator",
                'eps': "07_Illustrator",
                'psd': "08_Photoshop",
                'psb': "08_Photoshop",
                'aep': "09_AfterEffects",
                'aepx': "09_AfterEffects",
                'prproj': "10_PremierePro",
                'prfpset': "10_PremierePro",
                'obj': "11_3D_DATA",
                'fbx': "11_3D_DATA",
                'c4d': "11_3D_DATA",
                'ma': "11_3D_DATA",
                'mb': "11_3D_DATA",
                'blend': "11_3D_DATA",
                '3ds': "11_3D_DATA",
                'ffx': "12_EFFECT",
                'aex': "12_EFFECT"
            };

            return extensionMap[fileExtension] || "98_OTHER";
        }
    }
    return null;
}

// AEPフォルダー内で必要なフォルダーを特定する関数
function getRequiredFolders(folder) {
    var requiredFolders = [];

    // フォルダー内の全アイテムをチェック
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item.parentFolder === folder) {
            var folderType = determineItemFolderType(item);
            if (folderType && !arrayContains(requiredFolders, folderType)) {
                requiredFolders.push(folderType);
            }
        }
    }

    return requiredFolders;
}

// 既存のSolidフォルダーをチェックしてリネームする
function checkAndRenameSolidFolders(parentFolder) {
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item instanceof FolderItem &&
            item.parentFolder === parentFolder &&
            isSolidFolder(item.name)) {
            item.name = folderStructure.solidFolder;
        }
    }
}

// 再帰的にフォルダを作成する関数
function createFolderStructure(parentFolder, isRoot) {
    var folders = {};

    // 既存の「平面」または「Solid」フォルダーをチェック
    checkAndRenameSolidFolders(parentFolder);

    // 既存のSOLIDフォルダーを取得
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item instanceof FolderItem &&
            item.parentFolder === parentFolder &&
            item.name === folderStructure.solidFolder) {
            folders[folderStructure.solidFolder] = item;
            break;
        }
    }

    if (isRoot) {
        // ルートフォルダーには全カテゴリーを作成
        if (!folders[folderStructure.solidFolder]) {
            folders[folderStructure.solidFolder] = createOrGetFolder(folderStructure.solidFolder, parentFolder);
        }
        for (var i = 0; i < folderStructure.categories.length; i++) {
            var name = folderStructure.categories[i];
            folders[name] = createOrGetFolder(name, parentFolder);
        }
    } else {
        // AEPフォルダー内には必要なフォルダーのみ作成
        var requiredFolders = getRequiredFolders(parentFolder);
        if (!folders[folderStructure.solidFolder]) {
            folders[folderStructure.solidFolder] = createOrGetFolder(folderStructure.solidFolder, parentFolder);
        }
        for (var j = 0; j < requiredFolders.length; j++) {
            var folderName = requiredFolders[j];
            folders[folderName] = createOrGetFolder(folderName, parentFolder);
        }
    }

    return folders;
}

// フォルダを作成または取得する関数
function createOrGetFolder(name, parent) {
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item instanceof FolderItem && item.name === name && item.parentFolder === parent) {
            return item;
        }
    }
    var newFolder = app.project.items.addFolder(name);
    newFolder.parentFolder = parent;
    newFolder.label = getRandomLabelColor();
    return newFolder;
}

// 全アイテムの再帰的な整理
function organizeAllItems(parentFolder, rootFolders) {
    var currentFolders = rootFolders;

    // 現在のフォルダーがAEPフォルダーの場合、そのフォルダー用の構造を作成
    if (isAepFolder(parentFolder.name)) {
        currentFolders = createFolderStructure(parentFolder, false);
    }

    // 現在のフォルダー内のアイテムを整理
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item.parentFolder === parentFolder) {
            if (item instanceof FolderItem) {
                if (isAepFolder(item.name)) {
                    // AEPフォルダーは AfterEffects フォルダーに移動
                    item.parentFolder = rootFolders["09_AfterEffects"];
                    // AEPフォルダー内に必要なフォルダー構造を作成して整理
                    var aepFolders = createFolderStructure(item, false);
                    organizeAllItems(item, rootFolders);
                } else if (!isScriptGeneratedFolder(item.name) && parentFolder === app.project.rootFolder) {
                    // ルートの未分類フォルダーは OTHER に移動
                    item.parentFolder = rootFolders["98_OTHER"];
                }
                // サブフォルダーも再帰的に処理
                organizeAllItems(item, rootFolders);
            } else {
                // 通常のアイテムを整理
                var folderType = determineItemFolderType(item);
                if (folderType && currentFolders[folderType]) {
                    item.parentFolder = currentFolders[folderType];
                }
            }
        }
    }
}

function getRandomLabelColor() {
    return Math.floor(Math.random() * 16);
}

function isRenderComp(name) {
    return name.toLowerCase().indexOf("render") !== -1;
}

function isFolderEmpty(folder) {
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item.parentFolder === folder) {
            return false;
        }
    }
    return true;
}

// スクリプトで生成したフォルダーの保護をルートフォルダーのみに限定
function isProtectedFolder(folder, isRoot) {
    if (!isRoot) return false;
    return isScriptGeneratedFolder(folder.name);
}

// 空フォルダー削除関数を再帰的に実行
function removeEmptyFolders(parentFolder, isRoot) {
    var foldersToCheck = [];

    // 全フォルダーを収集
    for (var i = 1; i <= app.project.items.length; i++) {
        var item = app.project.items[i];
        if (item instanceof FolderItem && item.parentFolder === parentFolder) {
            // スクリプトで生成したフォルダーの保護判定(ルートのみ)
            if (!isProtectedFolder(item, isRoot)) {
                foldersToCheck.push(item);
            }
            // AEPフォルダー内の空フォルダーチェック
            if (isAepFolder(item.name)) {
                removeEmptyFolders(item, false);
            }
        }
    }

    // 空のフォルダーを削除
    for (var k = 0; k < foldersToCheck.length; k++) {
        if (isFolderEmpty(foldersToCheck[k])) {
            foldersToCheck[k].remove();
        }
    }
}

main();

 

This topic has been closed for replies.

1 reply

Participating Frequently
November 16, 2024
単純にisAepFolderisSolidFolderisScriptGeneratedFolderの3つの関数が
このコードて定義されていませんコード全体から察するには
関数を削除してしまったが、書いている過程によってグローバル空間に
関数の定義が残ってしまったのかなと思います。
 
CyifonAuthor
Inspiring
November 16, 2024

消したのかなと思ってます。これで一応やりたいことはかけてるんですが、次はfolderName.toLowerCase().includesは未定義です。とエラーが出るようになりました。

// AEPをフォルダー名に含むかを判定
function isAepFolder(folderName) {
    return folderName.toLowerCase().endsWith(".aep") || folderName.toLowerCase().endsWith(".aepx");
}

// SOLIDとか平面をフォルダー名に含むかを判定
function isSolidFolder(folderName) {
    return folderName.toLowerCase().includes("solid") || folderName.toLowerCase().includes("平面");
}

// スクリプトによって生成されたフォルダーか判定(ルートのフォルダーは中身がからでも残したい)
function isScriptGeneratedFolder(folderName) {
    var scriptGeneratedFolders = [
        folderStructure.solidFolder,
        "01_COMP",
        "02_IMAGE",
        "03_AUDIO",
        "04_VIDEO",
        "05_IMAGE_SEQ",
        "06_TEXT",
        "07_Illustrator",
        "08_Photoshop",
        "09_AfterEffects",
        "10_PremierePro",
        "11_3D_DATA",
        "12_EFFECT",
        "98_OTHER",
        "99_RENDER"
    ];
    return scriptGeneratedFolders.includes(folderName);
}



Participating Frequently
November 16, 2024

ExtendScriptは古い規格でString.includes()は新しめのjavascriptの機能なので、標準では存在しない機能です。

そのような新しいjavascriptの機能ですが便利なものもいくつかあり、すべての機能ではないですがいくつか古い規格のjavascriptでも使用できるようにすることが出来ます

それをポリフィルといいAEでもいくつか採用されています

 

ポリフィルした機能はAE全体に作用するため、何らかの他のスクリプト(どこかからもらった物、買った物などを含む)で独自にポリフィルされた場合、そのAE上の全スクリプトで使用できるようになります。

 

自分のAEで試したところStringに対してincludes()という命令が使用できないので、おそらくそちらのPCの何らかのスクリプトまたはエクステンションやプラグイン、でStringに対してincludes()がポリフィルされているのかと思います。

 

確実にString.include()を実行させたい場合は自分でポリフィルするしかないのですが、それが難しい場合ES3の機能だけを用いてコードを書いた方がよいと思います。

この場合はincludes()ではなくindexOf()で書いた方がよいのではないでしょうか。

 

コードの修正などはChatGPTを活用するとうまくいくと思います。その際にES3の規格で書いて欲しいなどと付け加えるとExtendScriptでも動くようなコードを書いてくれることが多いです。

 

AEのデフォルトでポリフィルされている詳細は以下のリンクになります。

https://qiita.com/tetsuoh/items/186bbcad3305ffeccbc5