Question
自作したファイル整理スクリプトが不明のエラーを出すようになってしまった。
かなり複雑な機能を実装しているフォルダー整理スクリプトなのですが、数時間前は正常に動作していたのですが、開発を進めるために再度実行しようとしたら原因が特定できないエラーに遭遇するようになりました。エラーは、「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();
