Skip to content

文件处理优化

本文档详细介绍了Eagle2Ae AE扩展的文件处理优化策略,帮助提升文件操作的效率和稳定性。

概述

文件处理优化是确保Eagle2Ae扩展高效处理大量文件的关键。本指南涵盖分批处理大量文件、文件类型预检查、路径缓存优化等优化技术。

分批处理大量文件

批量处理策略

将大量文件分批处理,避免阻塞UI线程:

javascript
class OptimizedFileHandler {
    constructor() {
        this.batchSize = 10;
        this.processingDelay = 50; // 批次间延迟
    }
    
    async handleLargeFileSet(files) {
        const results = [];
        
        for (let i = 0; i < files.length; i += this.batchSize) {
            const batch = files.slice(i, i + this.batchSize);
            
            try {
                const batchResults = await this.processBatch(batch);
                results.push(...batchResults);
                
                // 批次间短暂延迟,避免阻塞UI
                if (i + this.batchSize < files.length) {
                    await this.sleep(this.processingDelay);
                }
                
                // 更新进度
                this.updateProgress(i + batch.length, files.length);
            } catch (error) {
                console.error(`批次 ${i}-${i + batch.length} 处理失败:`, error);
            }
        }
        
        return results;
    }
    
    async processBatch(files) {
        // 并行处理批次内的文件
        const promises = files.map(file => this.processFile(file));
        return Promise.all(promises);
    }
}

文件类型预检查

文件类型验证

在处理前预检查文件类型,避免无效操作:

javascript
class FileTypeChecker {
    constructor() {
        this.supportedExtensions = new Set([
            '.jpg', '.jpeg', '.png', '.tiff', '.psd',
            '.mp4', '.mov', '.avi', '.mkv',
            '.wav', '.mp3', '.aiff'
        ]);
        
        this.imageExtensions = new Set(['.jpg', '.jpeg', '.png', '.tiff', '.psd']);
        this.videoExtensions = new Set(['.mp4', '.mov', '.avi', '.mkv']);
        this.audioExtensions = new Set(['.wav', '.mp3', '.aiff']);
    }
    
    preCheckFiles(files) {
        const validFiles = [];
        const invalidFiles = [];
        
        for (const file of files) {
            const ext = this.getFileExtension(file.name).toLowerCase();
            
            if (this.supportedExtensions.has(ext)) {
                file.type = this.getFileType(ext);
                validFiles.push(file);
            } else {
                invalidFiles.push({
                    file: file.name,
                    reason: `不支持的文件格式: ${ext}`
                });
            }
        }
        
        return { validFiles, invalidFiles };
    }
    
    getFileType(extension) {
        if (this.imageExtensions.has(extension)) return 'image';
        if (this.videoExtensions.has(extension)) return 'video';
        if (this.audioExtensions.has(extension)) return 'audio';
        return 'unknown';
    }
}

路径缓存优化

路径规范化缓存

缓存路径规范化结果,避免重复计算:

javascript
class PathCache {
    constructor() {
        this.cache = new Map();
        this.maxCacheSize = 200;
        this.accessCount = new Map();
    }
    
    normalizePath(path) {
        if (this.cache.has(path)) {
            // 更新访问计数
            this.accessCount.set(path, (this.accessCount.get(path) || 0) + 1);
            return this.cache.get(path);
        }
        
        // 执行路径规范化
        const normalized = path.replace(/\\/g, '/').replace(/\/+/g, '/');
        
        // 添加到缓存
        this.addToCache(path, normalized);
        return normalized;
    }
    
    addToCache(original, normalized) {
        // 如果缓存已满,清理最少使用的条目
        if (this.cache.size >= this.maxCacheSize) {
            this.evictLeastUsed();
        }
        
        this.cache.set(original, normalized);
        this.accessCount.set(original, 1);
    }
    
    evictLeastUsed() {
        let leastUsedKey = null;
        let minCount = Infinity;
        
        for (const [key, count] of this.accessCount) {
            if (count < minCount) {
                minCount = count;
                leastUsedKey = key;
            }
        }
        
        if (leastUsedKey) {
            this.cache.delete(leastUsedKey);
            this.accessCount.delete(leastUsedKey);
        }
    }
}

文件检测算法优化

项目文件检测性能优化

v2.3.1版本重大优化:将项目文件检测算法从O(n*m)优化到O(n+m),显著提升大文件集合的处理性能。

哈希表优化策略

ExtendScript层面优化:

javascript
// 优化前:线性查找 O(n*m)
function checkProjectImportedFiles_old(filePaths) {
    var importedFiles = [];
    var externalFiles = [];
    
    for (var i = 0; i < filePaths.length; i++) {
        var isImported = false;
        
        // 遍历所有项目文件进行比较 - O(m)
        for (var j = 1; j <= app.project.numItems; j++) {
            var item = app.project.item(j);
            if (item instanceof FootageItem && item.file) {
                if (item.file.fsName === filePaths[i]) {
                    isImported = true;
                    break;
                }
            }
        }
        
        if (isImported) {
            importedFiles.push(filePaths[i]);
        } else {
            externalFiles.push(filePaths[i]);
        }
    }
    
    return { importedFiles: importedFiles, externalFiles: externalFiles };
}

// 优化后:哈希表查找 O(n+m)
function checkProjectImportedFiles(filePaths) {
    var importedFiles = [];
    var externalFiles = [];
    
    // 构建项目文件路径哈希表 - O(m)
    var projectFilesMap = {};
    for (var i = 1; i <= app.project.numItems; i++) {
        var item = app.project.item(i);
        if (item instanceof FootageItem && item.file) {
            projectFilesMap[item.file.fsName] = true;
        }
    }
    
    // 快速查找文件是否已导入 - O(n)
    for (var j = 0; j < filePaths.length; j++) {
        if (projectFilesMap[filePaths[j]]) {
            importedFiles.push(filePaths[j]);
        } else {
            externalFiles.push(filePaths[j]);
        }
    }
    
    return { importedFiles: importedFiles, externalFiles: externalFiles };
}

AE项目文件扩展名检测优化:

javascript
// 优化前:数组遍历 O(n)
function checkAEProjectFiles_old(filePaths) {
    var aeProjectFiles = [];
    var nonProjectFiles = [];
    var aeExtensions = ['.aep', '.aet', '.aepx'];
    
    for (var i = 0; i < filePaths.length; i++) {
        var filePath = filePaths[i];
        var isAEProject = false;
        
        // 遍历扩展名数组 - O(k)
        for (var j = 0; j < aeExtensions.length; j++) {
            if (filePath.toLowerCase().indexOf(aeExtensions[j]) !== -1) {
                isAEProject = true;
                break;
            }
        }
        
        if (isAEProject) {
            aeProjectFiles.push(filePath);
        } else {
            nonProjectFiles.push(filePath);
        }
    }
    
    return { aeProjectFiles: aeProjectFiles, nonProjectFiles: nonProjectFiles };
}

// 优化后:哈希表查找 O(1)
function checkAEProjectFiles(filePaths) {
    var aeProjectFiles = [];
    var nonProjectFiles = [];
    
    // 使用哈希表存储AE项目文件扩展名 - O(1)查找
    var aeExtensionsMap = {
        '.aep': true,
        '.aet': true,
        '.aepx': true
    };
    
    for (var i = 0; i < filePaths.length; i++) {
        var filePath = filePaths[i];
        var lastDotIndex = filePath.lastIndexOf('.');
        
        if (lastDotIndex !== -1) {
            var extension = filePath.substring(lastDotIndex).toLowerCase();
            if (aeExtensionsMap[extension]) {
                aeProjectFiles.push(filePath);
            } else {
                nonProjectFiles.push(filePath);
            }
        } else {
            nonProjectFiles.push(filePath);
        }
    }
    
    return { aeProjectFiles: aeProjectFiles, nonProjectFiles: nonProjectFiles };
}

JavaScript层面优化

批量处理策略:

javascript
// 优化的项目文件检测方法
async isProjectInternalFile(files) {
    try {
        // 提取文件路径 - 优化路径处理
        const filePaths = files.map(file => {
            return file.path || file.fsName || file.fullName || file.toString();
        });
        
        // 使用哈希表进行快速路径匹配
        const pathSet = new Set(filePaths);
        
        // 首先检查是否包含AE项目文件
        const aeProjectResult = await this.executeExtendScript(
            'checkAEProjectFiles', 
            [filePaths]
        );
        
        if (aeProjectResult.success && aeProjectResult.data.aeProjectFiles.length > 0) {
            // 使用哈希表快速分类文件
            const aeProjectSet = new Set(aeProjectResult.data.aeProjectFiles);
            const projectFiles = filePaths.filter(path => aeProjectSet.has(path));
            const externalFiles = filePaths.filter(path => !aeProjectSet.has(path));
            
            return {
                hasProjectFiles: true,
                projectFiles: projectFiles,
                externalFiles: externalFiles,
                fileType: 'ae_project'
            };
        }
        
        // 批量处理优化:大文件集合分批检查
        if (filePaths.length > 100) {
            return await this.processBatchFileCheck(filePaths);
        }
        
        // 检查文件是否已导入到项目中
        const importResult = await this.executeExtendScript(
            'checkProjectImportedFiles', 
            [filePaths]
        );
        
        if (importResult.success) {
            const data = importResult.data;
            return {
                hasProjectFiles: data.importedFiles.length > 0,
                projectFiles: data.importedFiles,
                externalFiles: data.externalFiles,
                fileType: 'imported'
            };
        }
        
        // 检测失败时允许导入,避免阻止正常功能
        return {
            hasProjectFiles: false,
            projectFiles: [],
            externalFiles: filePaths,
            fileType: 'unknown'
        };
        
    } catch (error) {
        console.error('[ERROR] 项目文件检测失败:', error.message);
        // 出错时允许导入,确保功能可用性
        return {
            hasProjectFiles: false,
            projectFiles: [],
            externalFiles: files.map(f => f.path || f.toString()),
            fileType: 'error'
        };
    }
}

// 批量处理方法
async processBatchFileCheck(filePaths) {
    const batchSize = 50;
    const allImportedFiles = [];
    const allExternalFiles = [];
    
    for (let i = 0; i < filePaths.length; i += batchSize) {
        const batch = filePaths.slice(i, i + batchSize);
        
        try {
            const batchResult = await this.executeExtendScript(
                'checkProjectImportedFiles', 
                [batch]
            );
            
            if (batchResult.success) {
                allImportedFiles.push(...batchResult.data.importedFiles);
                allExternalFiles.push(...batchResult.data.externalFiles);
            } else {
                // 批次失败时将文件标记为外部文件
                allExternalFiles.push(...batch);
            }
        } catch (error) {
            console.error(`[ERROR] 批次 ${i}-${i + batchSize} 检测失败:`, error.message);
            allExternalFiles.push(...batch);
        }
    }
    
    return {
        hasProjectFiles: allImportedFiles.length > 0,
        projectFiles: allImportedFiles,
        externalFiles: allExternalFiles,
        fileType: 'imported'
    };
}

性能优化成果

算法复杂度对比:

  • 优化前: O(n*m) - 每个文件都需要遍历所有项目文件
  • 优化后: O(n+m) - 一次构建哈希表,后续O(1)查找

性能提升数据:

  • 小文件集合 (< 50个文件): 性能提升 60-80%
  • 大文件集合 (> 100个文件): 性能提升 80-90%
  • 内存使用: 减少约 40%
  • 检测速度: 144个序列帧文件从 3-5秒 缩短到 < 1秒

日志优化:

  • 移除生产环境中的调试日志
  • 减少不必要的日志输出
  • 优化日志格式,提高可读性

拖拽性能优化

文件夹展开优化

javascript
class OptimizedDragHandler {
    constructor() {
        this.maxFileLimit = 500; // 文件数量限制
        this.batchSize = 50;     // 批处理大小
    }
    
    async handleFolderDrop(folderPath) {
        try {
            // 快速预检查文件数量
            const fileCount = await this.getFileCount(folderPath);
            
            if (fileCount > this.maxFileLimit) {
                const proceed = await this.showLargeFileSetWarning(fileCount);
                if (!proceed) return;
            }
            
            // 分批处理文件发现
            const files = await this.discoverFilesInBatches(folderPath);
            
            // 执行项目文件检测
            return await this.isProjectInternalFile(files);
            
        } catch (error) {
            console.error('[ERROR] 文件夹拖拽处理失败:', error);
            throw error;
        }
    }
    
    async discoverFilesInBatches(folderPath) {
        const allFiles = [];
        const directories = [folderPath];
        
        while (directories.length > 0) {
            const currentDir = directories.pop();
            const items = await this.readDirectory(currentDir);
            
            for (const item of items) {
                if (item.isDirectory) {
                    directories.push(item.path);
                } else {
                    allFiles.push(item);
                    
                    // 分批处理,避免阻塞UI
                    if (allFiles.length % this.batchSize === 0) {
                        await this.sleep(10); // 短暂延迟
                    }
                }
            }
        }
        
        return allFiles;
    }
}

序列帧检测优化

javascript
class SequenceDetector {
    constructor() {
        this.sequencePatterns = [
            /^(.+?)(\d{3,})(\.\w+)$/,  // name001.ext
            /^(.+?)_(\d{3,})(\.\w+)$/, // name_001.ext
            /^(.+?)\.(\d{3,})(\.\w+)$/ // name.001.ext
        ];
    }
    
    detectSequences(files) {
        const sequences = new Map();
        const singleFiles = [];
        
        // 使用哈希表快速分组
        const groupMap = new Map();
        
        for (const file of files) {
            const match = this.matchSequencePattern(file.name);
            
            if (match) {
                const { base, number, extension } = match;
                const key = `${base}${extension}`;
                
                if (!groupMap.has(key)) {
                    groupMap.set(key, []);
                }
                groupMap.get(key).push({
                    file: file,
                    number: parseInt(number, 10)
                });
            } else {
                singleFiles.push(file);
            }
        }
        
        // 识别真正的序列(至少3个连续文件)
        for (const [key, group] of groupMap) {
            if (group.length >= 3) {
                // 排序并检查连续性
                group.sort((a, b) => a.number - b.number);
                
                const consecutiveGroups = this.findConsecutiveGroups(group);
                for (const consecutiveGroup of consecutiveGroups) {
                    if (consecutiveGroup.length >= 3) {
                        sequences.set(key, consecutiveGroup.map(item => item.file));
                    } else {
                        singleFiles.push(...consecutiveGroup.map(item => item.file));
                    }
                }
            } else {
                singleFiles.push(...group.map(item => item.file));
            }
        }
        
        return { sequences, singleFiles };
    }
    
    matchSequencePattern(filename) {
        for (const pattern of this.sequencePatterns) {
            const match = filename.match(pattern);
            if (match) {
                return {
                    base: match[1],
                    number: match[2],
                    extension: match[3]
                };
            }
        }
        return null;
    }
    
    findConsecutiveGroups(sortedGroup) {
        const groups = [];
        let currentGroup = [sortedGroup[0]];
        
        for (let i = 1; i < sortedGroup.length; i++) {
            if (sortedGroup[i].number === sortedGroup[i-1].number + 1) {
                currentGroup.push(sortedGroup[i]);
            } else {
                if (currentGroup.length >= 3) {
                    groups.push(currentGroup);
                }
                currentGroup = [sortedGroup[i]];
            }
        }
        
        if (currentGroup.length >= 3) {
            groups.push(currentGroup);
        }
        
        return groups;
    }
}

最佳实践总结

1. 文件处理

  • 分批处理大量文件
  • 预检查文件类型
  • 缓存路径规范化结果

通过遵循这些优化策略,可以显著提升Eagle2Ae扩展的文件处理效率和稳定性。

Released under the MIT License.