Question
自作したスクリプトを実行すると、スクリプトが走ったあとにAeが一時的に消えてしまう
タイトルの通りです。スクリプトの開発中に、コードを実行したらAeが閉じられるようになりました。(Aeは終了したのではなく、ほんとうに見えなくなるだけです。タスクバーからAeを押すとまた見えます)
なお、スクリプトの機能は正常に動作しています。
なぜでしょうか?
// ユーザー設定を格納する
var userPreferences = {
keepEmptyFolders: true, // 空フォルダーを残すかどうか
useFootageFolder: false // フッテージフォルダーを使用するかどうか
};
// フォルダ構造の定義
var folderStructure = {
solidFolder: "00_SOLID", // 平面レイヤー用フォルダー
footageFolder: "02_FOOTAGE", // フッテージまとめ用フォルダー
// メインカテゴリー一覧
categories: [
"01_COMP", // コンポジション
"02_IMAGE", // 画像ファイル
"03_AUDIO", // 音声ファイル
"04_VIDEO", // 動画ファイル
"05_IMAGE_SEQ", // 画像シーケンス
"06_TEXT", // テキストファイル
"07_Illustrator", // Illustratorファイル
"08_Photoshop", // Photoshopファイル
"09_AfterEffects", // AfterEffectsファイル
"10_PremierePro", // Premiere Proファイル
"11_3D_DATA", // 3Dデータ
"12_EFFECT", // エフェクト
"98_OTHER", // その他
"99_RENDER" // レンダリング用
],
// フッテージフォルダーに含めるカテゴリー
footageCategories: [
"02_IMAGE",
"03_AUDIO",
"04_VIDEO",
"05_IMAGE_SEQ",
"06_TEXT",
"07_Illustrator",
"08_Photoshop",
"09_AfterEffects",
"10_PremierePro",
"11_3D_DATA"
]
};
/**
* ES3互換の配列検索関数
* @9397041 {Array} array 検索対象の配列
* @9397041 {*} searchElement 検索する要素
* @Returns {number} 見つかった位置のインデックス。見つからない場合は-1
*/
function arrayIndexOf(array, searchElement) {
for (var i = 0; i < array.length; i++) {
if (array[i] === searchElement) {
return i;
}
}
return -1;
}
/**
* Solidフォルダーかどうかを判定
* @9397041 {string} name フォルダー名
* @Returns {boolean} Solidフォルダーならtrue
*/
function isSolidFolder(name) {
var lowerName = name.toLowerCase();
return lowerName === "solid" ||
lowerName === "平面" ||
lowerName === folderStructure.solidFolder.toLowerCase();
}
/**
* 進捗表示ウィンドウを作成する関数
*/
function createProgressWindow(title) {
var win = new Window("palette", title, undefined, { closeButton: false });
win.orientation = "column";
win.alignChildren = "fill";
win.spacing = 10;
win.margins = 20;
// ステータステキスト
var statusText = win.add("statictext", undefined, "準備中...");
statusText.preferredSize.width = 300;
// プログレスバー
var progressBar = win.add("progressbar", undefined, 0, 100);
progressBar.preferredSize.width = 300;
progressBar.preferredSize.height = 20;
win.show();
return {
window: win,
setProgress: function (percent, status) {
progressBar.value = percent;
statusText.text = status;
win.update();
},
close: function () {
win.close();
}
};
}
/**
* フォルダー内の全アイテムを再帰的に収集する関数
*/
function collectAllItems(folder) {
var items = [];
var folders = [];
function traverse(currentFolder) {
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (isDescendantOf(item, currentFolder)) {
if (item instanceof FolderItem) {
folders.push(item);
if (item.parentFolder === currentFolder) {
traverse(item);
}
} else {
items.push(item);
}
}
}
}
traverse(folder);
return {
items: items,
folders: folders
};
}
/**
* AEPフォルダーかどうかを判定
* @9397041 {string} name フォルダー名
* @Returns {boolean} AEPフォルダーならtrue
*/
function isAepFolder(name) {
return /\.aep$/i.test(name);
}
/**
* スクリプト生成フォルダーの判定を強化
*/
function isScriptGeneratedFolder(name) {
// 厳密な形式チェック: 2桁の数字_で始まる
var prefixPattern = /^(\d{2})_/;
if (!prefixPattern.test(name)) {
return false;
}
// 定義済みフォルダー名と完全一致するかチェック
return arrayIndexOf(folderStructure.categories, name) !== -1 ||
name === folderStructure.solidFolder ||
name === folderStructure.footageFolder;
}
/**
* フォルダー内に未整理のアイテムがあるかチェック
* @9397041 {FolderItem} folder チェック対象のフォルダー
* @9397041 {Object} folders フォルダー構造オブジェクト
* @Returns {boolean} 未整理アイテムがあればtrue
*/
function hasUnorganizedItems(folder, folders) {
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item.parentFolder === folder) {
if (item instanceof FolderItem) {
if (!isScriptGeneratedFolder(item.name) && !isAepFolder(item.name)) {
return true;
}
} else {
var targetFolder = determineItemType(item);
if (targetFolder && folders[targetFolder] && item.parentFolder !== folders[targetFolder]) {
return true;
}
}
}
}
return false;
}
/**
* フォルダー構成を再構築する関数
* 既存のフォルダー構造を初期化し、アイテムを適切に再配置する
*/
function reorganizeFolders() {
var i, j;
var itemsToProcess = [];
var aepFolders = [];
// まず、非AEPフォルダー内のアイテムをルートに移動
for (i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item.parentFolder !== app.project.rootFolder) {
var parent = item.parentFolder;
var isInAep = false;
// AEPフォルダー内のアイテムかチェック
while (parent && parent !== app.project.rootFolder) {
if (isAepFolder(parent.name)) {
isInAep = true;
break;
}
parent = parent.parentFolder;
}
if (!isInAep && !(item instanceof FolderItem && item.name === "09_AfterEffects")) {
itemsToProcess.push(item);
}
}
}
// アイテムをルートに移動
for (i = 0; i < itemsToProcess.length; i++) {
try {
itemsToProcess[i].parentFolder = app.project.rootFolder;
} catch (e) {
continue;
}
}
// AfterEffectsフォルダーの処理
if (userPreferences.useFootageFolder) {
// FOOTAGEフォルダーを探すか作成
var footageFolder = null;
for (i = 1; i <= app.project.items.length; i++) {
item = app.project.items[i];
if (item instanceof FolderItem &&
item.name === folderStructure.footageFolder &&
item.parentFolder === app.project.rootFolder) {
footageFolder = item;
break;
}
}
if (!footageFolder) {
footageFolder = app.project.items.addFolder(folderStructure.footageFolder);
footageFolder.parentFolder = app.project.rootFolder;
}
// ルートのAfterEffectsフォルダーをFOOTAGEに移動
var aeFolder = null;
for (i = 1; i <= app.project.items.length; i++) {
item = app.project.items[i];
if (item instanceof FolderItem &&
item.name === "09_AfterEffects" &&
item.parentFolder === app.project.rootFolder) {
item.parentFolder = footageFolder;
aeFolder = item;
break;
}
}
// FOOTAGEフォルダー内に既にAfterEffectsフォルダーがある場合は統合
if (aeFolder) {
for (i = 1; i <= app.project.items.length; i++) {
item = app.project.items[i];
if (item instanceof FolderItem &&
item.name === "09_AfterEffects" &&
item.parentFolder === footageFolder &&
item !== aeFolder) {
// 中身を移動
for (j = 1; j <= app.project.items.length; j++) {
var subItem = app.project.items[j];
if (subItem.parentFolder === item) {
subItem.parentFolder = aeFolder;
}
}
// 空になったフォルダーを削除
if (isFolderEmpty(item)) {
item.remove();
}
}
}
}
}
// 空フォルダーの削除
for (i = app.project.items.length; i >= 1; i--) {
try {
var currentItem = app.project.items[i];
if (currentItem instanceof FolderItem &&
currentItem.parentFolder === app.project.rootFolder &&
!isAepFolder(currentItem.name)) {
if (isFolderEmpty(currentItem)) {
currentItem.remove();
}
}
} catch (e) {
continue;
}
}
}
/**
* プレビューデータ生成関数の修正
*/
function generatePreviewData(useFootageFolder, keepEmptyFolders) {
var previewData = [];
var rootContent = scanRootLevelContent();
// ルートアイテムを追加
previewData.push(new PreviewItem("プロジェクトルート", 0, "root", true, true));
if (useFootageFolder) {
// SOLIDフォルダー
if (keepEmptyFolders || rootContent[folderStructure.solidFolder]) {
previewData.push(new PreviewItem(folderStructure.solidFolder, 1, "folder",
rootContent[folderStructure.solidFolder], false));
}
// COMPフォルダー
if (keepEmptyFolders || rootContent["01_COMP"]) {
previewData.push(new PreviewItem("01_COMP", 1, "folder",
rootContent["01_COMP"], false));
}
// FOOTAGEフォルダー
if (keepEmptyFolders || hasRootLevelFootageContent(rootContent)) {
var footageItem = new PreviewItem(folderStructure.footageFolder, 1, "folder",
hasRootLevelFootageContent(rootContent), false);
previewData.push(footageItem);
// フッテージサブフォルダー
for (var i = 0; i < folderStructure.footageCategories.length; i++) {
var category = folderStructure.footageCategories[i];
if (keepEmptyFolders || rootContent[category]) {
var subItem = new PreviewItem(category, 2, "folder",
rootContent[category], false);
subItem.parent = folderStructure.footageFolder;
previewData.push(subItem);
}
}
}
// その他のフォルダー
var remainingCategories = ["12_EFFECT", "98_OTHER", "99_RENDER"];
for (var j = 0; j < remainingCategories.length; j++) {
var cat = remainingCategories[j];
if (keepEmptyFolders || rootContent[cat]) {
previewData.push(new PreviewItem(cat, 1, "folder",
rootContent[cat], false));
}
}
} else {
var categories = folderStructure.categories.slice();
categories.unshift(folderStructure.solidFolder);
for (var k = 0; k < categories.length; k++) {
var cat = categories[k];
if (cat === "09_AfterEffects") {
// AfterEffectsフォルダーの特別処理
var hasAepContents = hasAepFiles();
if (keepEmptyFolders || hasAepContents) {
var aeFolder = new PreviewItem(cat, 1, "folder", true, hasAepContents);
previewData.push(aeFolder);
// AEPフォルダーを追加
if (hasAepContents) {
var aepFolders = getAepFolders();
for (var m = 0; m < aepFolders.length; m++) {
var aepPreviewItem = new PreviewItem(
aepFolders[m], 2, "folder", true, true);
aepPreviewItem.parent = "09_AfterEffects";
previewData.push(aepPreviewItem);
}
}
}
} else if (keepEmptyFolders || rootContent[cat]) {
previewData.push(new PreviewItem(cat, 1, "folder",
rootContent[cat], false));
}
}
}
return previewData;
}
// ルートレベルのフッテージコンテンツの存在チェック
function hasRootLevelFootageContent(rootContent) {
for (var i = 0; i < folderStructure.footageCategories.length; i++) {
if (rootContent[folderStructure.footageCategories[i]]) {
return true;
}
}
return false;
}
// 新しい補助関数: フッテージコンテンツの存在チェック
function hasAnyFootageContent(folderUsage) {
for (var i = 0; i < folderStructure.footageCategories.length; i++) {
if (folderUsage[folderStructure.footageCategories[i]]) {
return true;
}
}
return false;
}
/**
* AEPフォルダーの階層構造を取得する関数
*/
function getAepFolderHierarchy() {
var hierarchy = {};
var aepFolders = [];
// まずすべてのAEPフォルダーを収集
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item instanceof FolderItem && isAepFolder(item.name)) {
aepFolders.push(item);
}
}
// AEPフォルダーを階層構造から解放して同レベルに配置
for (var j = 0; j < aepFolders.length; j++) {
var folder = aepFolders[j];
hierarchy[folder.name] = {
name: folder.name,
children: []
};
}
return hierarchy;
}
// AEPフォルダーのリストを取得する補助関数
function getAepFolders() {
var aepFolders = [];
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item instanceof FolderItem && isAepFolder(item.name)) {
aepFolders.push(item.name);
}
}
return aepFolders;
}
// 特定のAEPフォルダーにコンテンツがあるかチェックする補助関数
function hasAepContent(aepName) {
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item.parentFolder && item.parentFolder.name === aepName) {
return true;
}
}
return false;
}
// フォルダー内のアイテム数を取得する補助関数の修正
function getFolderItemCount(folderName) {
var count = 0;
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item.parentFolder && item.parentFolder.name === folderName) {
count++;
}
}
return count;
}
/**
* プレビューアイテムのコンストラクター
* @9397041 {string} name フォルダー名
* @9397041 {number} level インデントレベル
* @9397041 {string} type アイテムタイプ ("folder" または "item")
* @9397041 {boolean} hasItems アイテムを含むかどうか
* @9397041 {boolean} isOpen 開いた状態かどうか
*/
function PreviewItem(name, level, type, hasItems, isOpen) {
this.name = name; // フォルダー名
this.level = level; // インデントレベル
this.type = type; // "folder" または "item"
this.hasItems = hasItems; // アイテムを含むかどうか
this.isOpen = isOpen; // 開いているかどうか
this.parent = null; // 親フォルダー名(必要に応じて設定)
}
// このコンストラクターは、フォルダー構造の定義やユーザー設定の直後、
// showSettingsDialog関数の前に配置してください。
/**
* 設定ダイアログを表示する関数
* @Returns {number} ダイアログの結果(1:OK, 2:キャンセル)
*/
/**
* 設定ダイアログを表示する関数
*/
/**
* 設定ダイアログを表示する関数
*/
function showSettingsDialog() {
// ダイアログ作成
var dialog = new Window("dialog", "フォルダー整理設定");
dialog.orientation = "column";
dialog.alignChildren = ["left", "top"];
dialog.spacing = 10;
dialog.margins = 16;
// 空フォルダー設定グループ
var keepEmptyGroup = dialog.add("panel", undefined, "空フォルダー設定");
keepEmptyGroup.orientation = "column";
keepEmptyGroup.alignChildren = ["left", "top"];
keepEmptyGroup.margins = [12, 18, 12, 12];
keepEmptyGroup.spacing = 8;
keepEmptyGroup.preferredSize.width = 400;
// 説明文を追加
var explanationText = keepEmptyGroup.add("statictext", undefined,
"プロジェクトのルートに空のフォルダーを残す。", { multiline: true });
explanationText.preferredSize.width = 360;
// ラジオボタングループ
var radioGroup = keepEmptyGroup.add("group");
radioGroup.orientation = "row";
radioGroup.spacing = 20;
var keepRadio = radioGroup.add("radiobutton", undefined, "残す");
var removeRadio = radioGroup.add("radiobutton", undefined, "削除する");
keepRadio.value = userPreferences.keepEmptyFolders;
removeRadio.value = !userPreferences.keepEmptyFolders;
// フォルダー構成設定グループ
var footageGroup = dialog.add("panel", undefined, "フォルダー構成設定");
footageGroup.orientation = "column";
footageGroup.alignChildren = ["left", "top"];
footageGroup.margins = [12, 18, 12, 12];
footageGroup.spacing = 8;
footageGroup.preferredSize.width = 400;
// チェックボックスを変数として定義
var useFootageCheck = footageGroup.add("checkbox", undefined,
"素材フォルダーをフッテージフォルダーにまとめる");
useFootageCheck.value = userPreferences.useFootageFolder;
// プレビューグループ
var previewGroup = dialog.add("panel", undefined, "フォルダー構成プレビュー");
previewGroup.orientation = "column";
previewGroup.alignChildren = ["left", "top"];
previewGroup.margins = [12, 18, 12, 12];
previewGroup.spacing = 4;
previewGroup.preferredSize.width = 400;
// 説明テキストを追加
var previewExplanation = previewGroup.add("statictext", undefined,
"※ プレビューではフォルダー構造のみが表示されます\n" +
" ▶ マークのあるフォルダーはダブルクリックで開閉できます(この機能は開発中ですが、実現できないかもしれません。)",
{ multiline: true });
previewExplanation.preferredSize.width = 360;
// プレビューリストを追加
var previewList = previewGroup.add("listbox", undefined, [], {
multiselect: false,
numberOfColumns: 1,
showHeaders: false,
columnWidths: [360]
});
previewList.preferredSize.height = 160;
previewList.preferredSize.width = 360;
// プレビュー更新関数
function updatePreview() {
var previewItems = generatePreviewData(useFootageCheck.value, keepRadio.value);
previewList.removeAll();
// プレビューアイテムを表示
for (var i = 0; i < previewItems.length; i++) {
var item = previewItems[i];
var indent = createIndent(item.level);
var marker = item.hasItems ? (item.isOpen ? "▼ " : "▶ ") : " ";
// 親が閉じている場合はスキップ
var shouldShow = true;
if (item.level > 1 && item.parent) {
for (var j = 0; j < previewItems.length; j++) {
if (previewItems[j].name === item.parent && !previewItems[j].isOpen) {
shouldShow = false;
break;
}
}
}
if (shouldShow) {
var displayText = indent + marker + item.name;
var listItem = previewList.add("item", displayText);
listItem.previewData = item;
}
}
if (!keepRadio.value) {
previewList.add("item", "");
previewList.add("item", "※ 空フォルダーを自動的に削除します。");
}
dialog.layout.layout(true);
}
// リストのダブルクリック検出と処理を設定
var lastClickTime = 0;
var clickCount = 0;
previewList.onClick = function () {
var currentTime = (new Date()).getTime();
if (currentTime - lastClickTime < 500) { // 500ミリ秒以内の2回目のクリック
clickCount++;
if (clickCount === 2) { // ダブルクリック検出
var selection = this.selection;
if (selection && selection.previewData && selection.previewData.hasItems) {
selection.previewData.isOpen = !selection.previewData.isOpen;
updatePreview();
}
clickCount = 0;
}
} else {
clickCount = 1;
}
lastClickTime = currentTime;
};
// イベントリスナーの設定
keepRadio.onClick = function () {
removeRadio.value = !keepRadio.value;
updatePreview();
};
removeRadio.onClick = function () {
keepRadio.value = !removeRadio.value;
updatePreview();
};
useFootageCheck.onClick = function () {
updatePreview();
};
// ボタングループ
var buttonGroup = dialog.add("group");
buttonGroup.orientation = "row";
buttonGroup.alignment = ["center", "top"];
buttonGroup.spacing = 10;
var okButton = buttonGroup.add("button", undefined, "OK");
var cancelButton = buttonGroup.add("button", undefined, "キャンセル");
okButton.preferredSize.width = 80;
cancelButton.preferredSize.width = 80;
// OKボタンのイベントハンドラ
okButton.onClick = function () {
userPreferences.keepEmptyFolders = keepRadio.value;
userPreferences.useFootageFolder = useFootageCheck.value;
dialog.close(1);
};
// キャンセルボタンのイベントハンドラ
cancelButton.onClick = function () {
dialog.close(2);
};
// 初期プレビューの表示
updatePreview();
return dialog.show();
}
// プレビュー用のルートレベルのコンテンツのみを確認する関数
function scanRootLevelContent() {
var content = {};
// 各カテゴリーを初期化
for (var i = 0; i < folderStructure.categories.length; i++) {
content[folderStructure.categories[i]] = false;
}
content[folderStructure.solidFolder] = false;
// ルートレベルのアイテムのみをスキャン
for (var j = 1; j <= app.project.items.length; j++) {
var item = app.project.items[j];
if (item.parentFolder === app.project.rootFolder) {
if (!(item instanceof FolderItem)) {
var type = determineItemType(item);
if (type) {
content[type] = true;
}
} else if (isAepFolder(item.name)) {
content["09_AfterEffects"] = true;
}
}
}
return content;
}
// 再構築用の完全なコンテンツスキャン関数
function scanAllContent(folder) {
var content = {};
function traverse(currentFolder) {
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (isDescendantOf(item, currentFolder)) {
if (!(item instanceof FolderItem)) {
var type = determineItemType(item);
if (type) {
content[type] = true;
}
}
}
}
}
traverse(folder);
return content;
}
/**
* アイテムの種類ごとのフォルダーの使用状況をチェックする関数
* @Returns {Object} 各フォルダーの使用状況を示すオブジェクト
*/
function getFolderUsageStatus() {
var usageStatus = {};
var i, item;
// 全カテゴリーを初期化
for (i = 0; i < folderStructure.categories.length; i++) {
usageStatus[folderStructure.categories[i]] = false;
}
usageStatus[folderStructure.solidFolder] = false;
// ルートレベルのアイテムのみをチェック
for (i = 1; i <= app.project.items.length; i++) {
try {
item = app.project.items[i];
// ルートレベルのアイテムのみを処理
if (item.parentFolder === app.project.rootFolder) {
if (item instanceof CompItem) {
if (item.name.toLowerCase().indexOf("render") !== -1) {
usageStatus["99_RENDER"] = true;
} else {
usageStatus["01_COMP"] = true;
}
} else if (item instanceof FootageItem) {
var type = determineItemType(item);
if (type) {
usageStatus[type] = true;
}
} else if (item instanceof FolderItem) {
if (isAepFolder(item.name)) {
usageStatus["09_AfterEffects"] = true;
} else if (!isScriptGeneratedFolder(item.name)) {
usageStatus["98_OTHER"] = true;
}
}
}
// AEPフォルダーの存在チェックのみ追加で行う
else if (item instanceof FolderItem &&
item.parentFolder === app.project.rootFolder &&
isAepFolder(item.name)) {
usageStatus["09_AfterEffects"] = true;
}
} catch (e) {
continue;
}
}
return usageStatus;
}
/**
* OTHERフォルダーが必要かどうかをチェックする関数
*/
function needsOtherFolder() {
// 未分類アイテムの存在をチェック
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
// ルートレベルのアイテムのみをチェック
if (item.parentFolder === app.project.rootFolder) {
if (item instanceof FolderItem) {
// AEPフォルダーとスクリプト生成フォルダー以外で、かつ中身があるものを探す
if (!isAepFolder(item.name) && !isScriptGeneratedFolder(item.name)) {
for (var j = 1; j <= app.project.items.length; j++) {
if (app.project.items[j].parentFolder === item) {
return true;
}
}
}
} else {
// フッテージアイテムの場合
var type = determineItemType(item);
if (!type || (type === "98_OTHER" && !isScriptGeneratedFolder(item.name))) {
return true;
}
}
}
}
return false;
}
function getAepFolderItemCount() {
var count = 0;
try {
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item instanceof FolderItem && isAepFolder(item.name)) {
count++;
}
}
} catch (e) {
// エラー時は0を返す
}
return count;
}
/**
* AEPファイルの存在をチェックする関数
*/
function hasAepFiles() {
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item instanceof FolderItem && isAepFolder(item.name)) {
return true;
}
}
return false;
}
/**
* アイテムが指定フォルダーの直接の子かどうかを判定
*/
function isDirectChild(item, parentFolder) {
return item.parentFolder === parentFolder;
}
/**
* メイン処理関数
* スクリプトのメインの実行フローを制御
*/
// main関数の修正(AfterEffects が閉じる問題の対応)
function main() {
if (!app.project) {
alert("プロジェクトが開かれていません。プロジェクトを開いてから実行してください。");
return;
}
// app.beginUndoGroup を try-catch の外に出す
app.beginUndoGroup("Organize Project Folders");
try {
// ダイアログを表示し、キャンセルされた場合は早期リターン
if (showSettingsDialog() !== 1) {
app.endUndoGroup();
return;
}
// プログレスバーを表示
var progress = createProgressWindow("フォルダーを整理中");
try {
// メイン処理
progress.setProgress(20, "フォルダー構造を再構築中...");
reorganizeFolders();
progress.setProgress(40, "フォルダー構造を作成中...");
var rootFolders = createFolderStructure(app.project.rootFolder, true);
if (userPreferences.useFootageFolder) {
progress.setProgress(60, "フッテージを整理中...");
organizeFootageFolders(rootFolders);
}
progress.setProgress(80, "アイテムを整理中...");
var hasUnorganizedItems;
do {
hasUnorganizedItems = false;
var changed = organizeItems(app.project.rootFolder, rootFolders);
hasUnorganizedItems = hasUnorganizedItems || changed;
} while (hasUnorganizedItems);
progress.setProgress(90, "AEPフォルダーを処理中...");
organizeAepFolders(rootFolders);
progress.setProgress(95, "空フォルダーを処理中...");
processEmptyFolders(app.project.rootFolder);
progress.setProgress(100, "完了");
} finally {
progress.close();
}
} catch (error) {
alert("エラーが発生しました:\n" + error.toString());
} finally {
// 必ず実行される
app.endUndoGroup();
}
}
/**
* AEPフォルダー内の整理を行う関数
* @9397041 {Object} rootFolders ルートフォルダー構造
* @Returns {boolean} 変更があったかどうか
*/
function organizeAepFolders(rootFolders) {
var hasChanges = false;
var aepParentFolder = rootFolders["09_AfterEffects"];
if (!aepParentFolder) return false;
// AEPフォルダーを収集
var aepFolders = [];
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item instanceof FolderItem &&
isDirectChild(item, aepParentFolder) &&
isAepFolder(item.name)) {
aepFolders.push(item);
}
}
// 各AEPフォルダーを処理
for (var j = 0; j < aepFolders.length; j++) {
var aepFolder = aepFolders[j];
reorganizeAepContents(aepFolder);
hasChanges = true;
}
return hasChanges;
}
/**
* ファイル拡張子に基づいてタイプを判定する補助関数
*/
function determineFileType(fileExt, isStill) {
// シーケンス画像の判定
if (isStill === false) {
if (/^(jpg|jpeg|png|tif|tiff|psd|tga|exr|dpx|cin|iff)$/i.test(fileExt)) {
return "05_IMAGE_SEQ";
}
}
var fileTypeMap = {
// 画像ファイル
'jpg': "02_IMAGE",
'jpeg': "02_IMAGE",
'png': "02_IMAGE",
'gif': "02_IMAGE",
'tif': "02_IMAGE",
'tiff': "02_IMAGE",
'bmp': "02_IMAGE",
'tga': "02_IMAGE",
'exr': "02_IMAGE",
'dpx': "02_IMAGE",
'iff': "02_IMAGE",
'webp': "02_IMAGE",
'heic': "02_IMAGE",
// Photoshopファイル
'psd': "08_Photoshop",
'psb': "08_Photoshop",
// Illustratorファイル
'ai': "07_Illustrator",
'eps': "07_Illustrator",
'pdf': "07_Illustrator",
// 音声ファイル
'wav': "03_AUDIO",
'mp3': "03_AUDIO",
'aac': "03_AUDIO",
'm4a': "03_AUDIO",
'aif': "03_AUDIO",
'aiff': "03_AUDIO",
'ogg': "03_AUDIO",
'wma': "03_AUDIO",
// 動画ファイル
'mp4': "04_VIDEO",
'mov': "04_VIDEO",
'avi': "04_VIDEO",
'wmv': "04_VIDEO",
'm4v': "04_VIDEO",
'mxf': "04_VIDEO",
'r3d': "04_VIDEO",
'mts': "04_VIDEO",
// テキストファイル
'txt': "06_TEXT",
'csv': "06_TEXT",
'rtf': "06_TEXT",
'json': "06_TEXT",
'xml': "06_TEXT"
};
return fileTypeMap[fileExt] || "98_OTHER";
}
/**
* アイテムの種類判定関数
* @9397041 {Item} item 判定するアイテム
* @Returns {string|null} アイテムの種類を示すフォルダー名、または null
*/
/**
* ファイル種別の判定を拡充
*/
function determineItemType(item) {
// アイテムの存在チェック
if (!item) return null;
try {
// CompItemの判定
if (item instanceof CompItem) {
try {
// nameプロパティへのアクセスを保護
var itemName = item.name;
if (itemName && itemName.toLowerCase().indexOf("render") !== -1) {
return "99_RENDER";
}
} catch (e) {
// nameプロパティへのアクセスに失敗した場合でもコンポジションとして扱う
}
return "01_COMP";
}
// FootageItemの判定
if (item instanceof FootageItem) {
try {
// mainSourceプロパティの存在確認
if (!item.mainSource) return "98_OTHER";
// Solid(平面レイヤー)の判定
if (item.mainSource instanceof SolidSource) {
return folderStructure.solidFolder;
}
// ファイルベースのフッテージの判定
if (item.mainSource instanceof FileSource) {
try {
// ファイル情報へのアクセスを保護
var file = item.mainSource.file;
if (!file) return "98_OTHER";
var fileName = file.name;
if (!fileName) return "98_OTHER";
// 拡張子の取得
var fileExt = fileName.split('.').pop().toLowerCase();
// シーケンス画像の判定
try {
if (item.mainSource.isStill === false) {
if (/^(jpg|jpeg|png|tif|tiff|psd|tga|exr|dpx|cin|iff)$/i.test(fileExt)) {
return "05_IMAGE_SEQ";
}
}
} catch (e) {
// isStillプロパティにアクセスできない場合は通常のファイルとして処理
}
// ファイルタイプの判定
var fileTypeMap = {
// 画像ファイル
'jpg': "02_IMAGE",
'jpeg': "02_IMAGE",
'png': "02_IMAGE",
'gif': "02_IMAGE",
'tif': "02_IMAGE",
'tiff': "02_IMAGE",
'bmp': "02_IMAGE",
'tga': "02_IMAGE",
'exr': "02_IMAGE",
'dpx': "02_IMAGE",
'iff': "02_IMAGE",
'webp': "02_IMAGE",
'heic': "02_IMAGE",
// Photoshopファイル
'psd': "08_Photoshop",
'psb': "08_Photoshop",
// Illustratorファイル
'ai': "07_Illustrator",
'eps': "07_Illustrator",
'pdf': "07_Illustrator",
// 音声ファイル
'wav': "03_AUDIO",
'mp3': "03_AUDIO",
'aac': "03_AUDIO",
'm4a': "03_AUDIO",
'aif': "03_AUDIO",
'aiff': "03_AUDIO",
'ogg': "03_AUDIO",
'wma': "03_AUDIO",
// 動画ファイル
'mp4': "04_VIDEO",
'mov': "04_VIDEO",
'avi': "04_VIDEO",
'wmv': "04_VIDEO",
'm4v': "04_VIDEO",
'mxf': "04_VIDEO",
'r3d': "04_VIDEO",
'mts': "04_VIDEO",
// テキストファイル
'txt': "06_TEXT",
'csv': "06_TEXT",
'rtf': "06_TEXT",
'json': "06_TEXT",
'xml': "06_TEXT"
};
// 該当する拡張子があればそのタイプを返す、なければOTHER
return fileTypeMap[fileExt] || "98_OTHER";
} catch (e) {
// ファイル情報の取得に失敗した場合
return "98_OTHER";
}
}
} catch (e) {
// mainSourceへのアクセスに失敗した場合
return "98_OTHER";
}
}
// 判定できない場合
return "98_OTHER";
} catch (e) {
// 完全に予期せぬエラーが発生した場合
return "98_OTHER";
}
}
/**
* AEPフォルダー内のアイテムを再整理する関数
*/
function reorganizeAepContents(aepFolder) {
var progress = createProgressWindow("AEPフォルダー内を再構築中");
try {
// Step 1: 既存のフォルダー構造を解体
progress.setProgress(10, "既存のフォルダー構造を解体中...");
var itemsToPreserve = [];
var innerAepFolders = [];
var aepFolderItems = {}; // 各AEPフォルダーに属するアイテムを保存
// 既存のフォルダー構造を走査し、すべてのアイテムを収集
function collectItems(folder, targetArray) {
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (isDescendantOf(item, folder)) {
if (item instanceof FolderItem) {
if (isAepFolder(item.name)) {
innerAepFolders.push(item);
aepFolderItems[item.id] = []; // 新しい配列を初期化
collectItems(item, aepFolderItems[item.id]); // アイテムを個別に収集
} else {
collectItems(item, targetArray);
}
} else {
targetArray.push(item);
}
}
}
}
collectItems(aepFolder, itemsToPreserve);
// Step 2: 新しいフォルダー構造を作成
progress.setProgress(30, "フォルダー構造を作成中...");
var folders = createFolderStructure(aepFolder, true);
// Step 3: 現在のAEPフォルダーのアイテムを振り分け
progress.setProgress(50, "アイテムを振り分け中...");
for (var j = 0; j < itemsToPreserve.length; j++) {
var currentItem = itemsToPreserve[j];
var type = determineItemType(currentItem);
if (type && folders[type]) {
try {
currentItem.parentFolder = folders[type];
} catch (e) {
continue;
}
}
}
// Step 4: 内部のAEPフォルダーを処理
if (innerAepFolders.length > 0) {
progress.setProgress(70, "内部のAEPフォルダーを処理中...");
var aeFolder = null;
for (var k = 1; k <= app.project.items.length; k++) {
if (app.project.items[k] instanceof FolderItem &&
app.project.items[k].name === "09_AfterEffects") {
aeFolder = app.project.items[k];
break;
}
}
if (!aeFolder) {
aeFolder = createOrGetFolder("09_AfterEffects", app.project.rootFolder);
}
// 内部のAEPフォルダーそれぞれに対して処理
for (var m = 0; m < innerAepFolders.length; m++) {
var innerAepFolder = innerAepFolders[m];
// 各AEPフォルダーのアイテムを保持したまま再構築
var innerFolders = createFolderStructure(innerAepFolder, true);
// 保存しておいたアイテムを振り分け
var innerItems = aepFolderItems[innerAepFolder.id] || [];
for (var n = 0; n < innerItems.length; n++) {
var innerItem = innerItems[n];
var innerType = determineItemType(innerItem);
if (innerType && innerFolders[innerType]) {
try {
innerItem.parentFolder = innerFolders[innerType];
} catch (e) {
continue;
}
}
}
// AEPフォルダーを移動
innerAepFolder.parentFolder = aeFolder;
progress.setProgress(
70 + ((m + 1) / innerAepFolders.length * 30),
"AEPフォルダーを処理中: " + (m + 1) + "/" + innerAepFolders.length
);
}
}
progress.setProgress(100, "完了");
} catch (e) {
alert("エラーが発生しました: " + e.toString());
} finally {
progress.close();
}
}
/**
* フォルダーの深さを取得する補助関数
*/
function getFolderDepth(folder, topFolder) {
var depth = 0;
var current = folder;
while (current && current !== topFolder) {
depth++;
current = current.parentFolder;
}
return depth;
}
/**
* フォルダーの内容を移動する補助関数
*/
function moveContents(sourceFolder, targetFolder) {
if (!sourceFolder || !targetFolder || sourceFolder === targetFolder) return;
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item.parentFolder === sourceFolder) {
try {
item.parentFolder = targetFolder;
} catch (e) {
// エラーログを記録するなどの処理をここに追加可能
}
}
}
}
/**
* 文字列が特定のプレフィックスで始まるかチェックする(ES3互換)
*/
function stringStartsWith(str, prefix) {
return str.indexOf(prefix) === 0;
}
/**
* フォルダー名に最も近いスクリプト生成フォルダーを見つける補助関数(ES3互換)
*/
function findMatchingFolder(folderName, newFolders) {
var numMatch = folderName.match(/^\d+/);
if (!numMatch) return null;
var folderNum = numMatch[0];
for (var key in newFolders) {
if (newFolders.hasOwnProperty(key)) {
if (stringStartsWith(key, folderNum)) {
return newFolders[key];
}
}
}
return null;
}
/**
* フォルダーの内容を種類に応じて再配置する補助関数
*/
function redistributeContents(folder, newFolders) {
for (var i = 1; i <= app.project.items.length; i++) {
var item = app.project.items[i];
if (item.parentFolder === folder) {
var targetType = determineItemType(item);
if (targetType && newFolders[targetType]) {
try {
item.parentFolder = newFolders[targetType];
} catch (e) {
// エラーログを記録するなどの処理をここに追加可能
}
}
}
}
}
/**
* アイテムが指定フォルダーの子孫かどうかを判定する関数を追加
*/
function isDescendantOf(item, ancestorFolder) {
if (!item || !ancestorFolder) return false;
var current = item.parentFolder;
while (current) {
if (current === ancestorFolder) {
return true;
}
current = current.parentFolder;
}
return false;
}
/**
* フォルダー内のアイテムを整理する関数
* @9397041 {FolderItem} parentFolder 親フォルダー
* @9397041 {Object} folders フォルダー構造
* @Returns {boolean} 変更があったかどうか
*/
function organizeItems(parentFolder, folders) {
var hasChanges = 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)) {
if (parentFolder === app.project.rootFolder) {
if (item.parentFolder !== folders["09_AfterEffects"]) {
item.parentFolder = folders["09_AfterEffects"];
hasChanges = true;
}
}
} else if (!isScriptGeneratedFolder(item.name) && parentFolder === app.project.rootFolder) {
if (item.parentFolder !== folders["98_OTHER"]) {
item.parentFolder = folders["98_OTHER"];
hasChanges = true;
}
}
} else {
var targetFolder = determineItemType(item);
if (targetFolder && folders[targetFolder] && item.parentFolder !== folders[targetFolder]) {
item.parentFolder = folders[targetFolder];
hasChanges = true;
}
}
}
}
return hasChanges;
}
/**
* フォルダーが空かどうかを判定する関数
* @9397041 {FolderItem} folder 判定するフォルダー
* @Returns {boolean} フォルダーが空ならtrue
*/
function isFolderEmpty(folder) {
if (!folder) return true;
try {
for (var i = 1; i <= app.project.items.length; i++) {
try {
var item = app.project.items[i];
if (item && item.parentFolder === folder) {
return false;
}
} catch (e) {
continue;
}
}
return true;
} catch (e) {
return true;
}
}
/**
* 指定されたフォルダーがAEPフォルダー内にあるかどうかを判定する関数
* @9397041 {FolderItem} folder 判定するフォルダー
* @Returns {boolean} AEPフォルダー内にあればtrue
*/
function isInsideAepFolder(folder) {
var parent = folder.parentFolder;
while (parent && parent !== app.project.rootFolder) {
if (isAepFolder(parent.name)) {
return true;
}
parent = parent.parentFolder;
}
return false;
}
/**
* プレビュー画面の更新処理を修正
*/
function updatePreview() {
var previewItems = generatePreviewData(useFootageCheck.value, keepRadio.value);
if (!previewList) return;
previewList.removeAll();
// プレビューアイテムを表示
for (var i = 0; i < previewItems.length; i++) {
var item = previewItems[i];
var indent = createIndent(item.level);
var marker = item.hasItems ? (item.isOpen ? "▼ " : "▶ ") : " ";
// 親が閉じている場合はスキップ
var shouldShow = true;
if (item.level > 1 && item.parent) {
for (var j = 0; j < previewItems.length; j++) {
if (previewItems[j].name === item.parent && !previewItems[j].isOpen) {
shouldShow = false;
break;
}
}
}
if (shouldShow) {
var displayText = indent + marker + item.name;
var listItem = previewList.add("item", displayText);
listItem.previewData = item;
}
}
if (!keepRadio.value) {
previewList.add("item", "");
previewList.add("item", "※ 空フォルダーは自動的に削除されます");
}
dialog.layout.layout(true);
}
/**
* インデントを作成する補助関数
*/
function createIndent(level) {
var indent = "";
for (var i = 0; i < level; i++) {
indent += " ";
}
return indent;
}
/**
* リストのダブルクリックを検出する代替機能
*/
function setupListClickHandler(list) {
var lastClickTime = 0;
var clickCount = 0;
list.onClick = function () {
var currentTime = (new Date()).getTime();
if (currentTime - lastClickTime < 500) { // 500ミリ秒以内の2回目のクリック
clickCount++;
if (clickCount === 2) { // ダブルクリック検出
var selection = this.selection;
if (selection && selection.previewData && selection.previewData.hasItems) {
selection.previewData.isOpen = !selection.previewData.isOpen;
updatePreview();
}
clickCount = 0;
}
} else {
clickCount = 1;
}
lastClickTime = currentTime;
};
}
/**
* フォルダー構造を作成する関数
* @9397041 {FolderItem} parentFolder 親フォルダー
* @9397041 {boolean} isRoot ルートレベルかどうか
* @Returns {Object} 作成されたフォルダー構造
*/
function createFolderStructure(parentFolder, isRoot) {
var folders = {};
// 基本フォルダー構造の作成
var categoriesToCreate = folderStructure.categories.slice();
categoriesToCreate.unshift(folderStructure.solidFolder);
// 各カテゴリーフォルダーの作成
for (var i = 0; i < categoriesToCreate.length; i++) {
var categoryName = categoriesToCreate[i];
// AEPフォルダー内では AfterEffects フォルダーを作成しない
if (isAepFolder(parentFolder.name) && categoryName === "09_AfterEffects") {
continue;
}
// フッテージフォルダー関連の処理
var isFootageCategory = arrayIndexOf(folderStructure.footageCategories, categoryName) !== -1;
var targetParent = parentFolder;
if (isRoot && userPreferences.useFootageFolder && isFootageCategory) {
// ルートレベルでフッテージフォルダーを使用する場合
if (!folders[folderStructure.footageFolder]) {
folders[folderStructure.footageFolder] = createOrGetFolder(folderStructure.footageFolder, parentFolder);
}
targetParent = folders[folderStructure.footageFolder];
}
folders[categoryName] = createOrGetFolder(categoryName, targetParent);
}
return folders;
}
/**
* フォルダーを作成または取得する関数
* @9397041 {string} name フォルダー名
* @9397041 {FolderItem} parent 親フォルダー
* @Returns {FolderItem} 作成または取得したフォルダー
*/
function createOrGetFolder(name, parent) {
var folder = null;
for (var i = 1; i <= app.project.items.length; i++) {
if (app.project.items[i] instanceof FolderItem &&
app.project.items[i].name === name &&
app.project.items[i].parentFolder === parent) {
folder = app.project.items[i];
break;
}
}
if (!folder) {
folder = app.project.items.addFolder(name);
folder.parentFolder = parent;
folder.label = Math.floor(Math.random() * 16);
}
return folder;
}
/**
* フッテージフォルダーの整理関数
* @9397041 {Object} rootFolders ルートフォルダー構造
*/
function organizeFootageFolders(rootFolders) {
if (!rootFolders[folderStructure.footageFolder]) {
return;
}
for (var i = 0; i < folderStructure.footageCategories.length; i++) {
var categoryName = folderStructure.footageCategories[i];
if (rootFolders[categoryName]) {
rootFolders[categoryName].parentFolder = rootFolders[folderStructure.footageFolder];
}
}
}
/**
* 空フォルダーを処理する関数
* @9397041 {FolderItem} folder 処理するフォルダー
*/
function processEmptyFolders(folder) {
var items = [];
for (var i = 1; i <= app.project.items.length; i++) {
if (app.project.items[i].parentFolder === folder) {
items.push(app.project.items[i]);
}
}
for (var j = 0; j < items.length; j++) {
if (items[j] instanceof FolderItem) {
if (isAepFolder(items[j].name)) {
// AEPフォルダー内は常に空フォルダーを削除
processEmptyFolders(items[j]);
} else {
processEmptyFolders(items[j]);
if (isFolderEmpty(items[j])) {
// ルートレベルのフォルダー
if (items[j].parentFolder === app.project.rootFolder) {
if (!userPreferences.keepEmptyFolders || !isScriptGeneratedFolder(items[j].name)) {
items[j].remove();
}
}
// AEPフォルダー内のフォルダー
else if (isInsideAepFolder(items[j])) {
items[j].remove();
}
// その他の空フォルダー
else if (!userPreferences.keepEmptyFolders) {
items[j].remove();
}
}
}
}
}
}
// スクリプトの実行
main();