import { readPsd, initializeCanvas } from 'ag-psd' import sharp from 'sharp' import fs from 'fs/promises' import { configureSharpRuntime } from '@/lib/sharp-runtime' configureSharpRuntime() // 尝试初始化 canvas(如果可用) try { // 动态导入 canvas(可选依赖) const canvas = require('canvas') if (canvas && canvas.createCanvas) { initializeCanvas(canvas.createCanvas) console.log('[PSD] Canvas initialized successfully') } } catch (error) { console.log('[PSD] Canvas not available, using fallback method') } interface PsdProcessResult { success: boolean previewPath?: string width?: number height?: number error?: string } /** * 从PSD文件生成预览图 * @param psdFilePath PSD文件路径 * @param outputPath 输出预览图路径 * @param maxSize 最大尺寸(宽或高),默认800px * @param maxHeight 最大高度限制,长图裁剪,默认1200px * @returns 处理结果 */ export async function generatePsdPreview( psdFilePath: string, outputPath: string, maxSize: number = 800, maxHeight: number = 1200 ): Promise { try { // 读取PSD文件 const psdBuffer = await fs.readFile(psdFilePath) console.log(`[PSD] 开始解析PSD文件,大小: ${psdBuffer.length} bytes`) // 解析PSD - 不跳过合成图像数据 const psd = readPsd(psdBuffer, { skipLayerImageData: true, // 跳过图层数据 skipCompositeImageData: false, // 不跳过合成图像数据(关键!) skipThumbnail: false }) if (!psd) { return { success: false, error: 'Failed to parse PSD file' } } console.log(`[PSD] 解析成功,尺寸: ${psd.width}x${psd.height}`) // 获取PSD的宽高 const width = psd.width || 0 const height = psd.height || 0 if (width === 0 || height === 0) { return { success: false, error: 'Invalid PSD dimensions' } } // 检查是否有合成图像数据 if (!psd.imageData || !psd.imageData.data) { console.log('[PSD] 未找到合成图像数据') return { success: false, error: 'No composite image data found in PSD file. Please manually upload a preview image.' } } console.log(`[PSD] 找到合成图像数据: ${psd.imageData.width}x${psd.imageData.height}`) const imageData = psd.imageData // 确保有图像数据 if (!imageData.data || imageData.data.length === 0) { return { success: false, error: 'Image data is empty' } } console.log(`[PSD] 图像数据大小: ${imageData.data.length} bytes`) // 将PSD图像数据转换为Buffer const rawBuffer = Buffer.from(imageData.data) // 创建Sharp实例 let sharpInstance = sharp(rawBuffer, { raw: { width: imageData.width, height: imageData.height, channels: 4 // RGBA } }) // 如果是长图(高度超过maxHeight),先裁剪顶部区域 if (imageData.height > maxHeight) { console.log(`[PSD] 检测到长图 ${imageData.width}x${imageData.height},裁剪到顶部${maxHeight}px`) sharpInstance = sharpInstance.extract({ left: 0, top: 0, width: imageData.width, height: maxHeight }) } // 然后进行缩放处理 await sharpInstance .resize(maxSize, maxSize, { fit: 'inside', withoutEnlargement: true }) .jpeg({ quality: 90, mozjpeg: true }) .toFile(outputPath) console.log(`[PSD] 预览图生成成功: ${outputPath}`) return { success: true, previewPath: outputPath, width, height } } catch (error) { console.error('Error generating PSD preview:', error) return { success: false, error: error instanceof Error ? error.message : 'Unknown error' } } } /** * 检查文件是否为PSD格式 * @param filePath 文件路径 * @returns 是否为PSD文件 */ export async function isPsdFile(filePath: string): Promise { try { const buffer = await fs.readFile(filePath, { encoding: null }) // PSD文件签名:8BPS return buffer.length > 4 && buffer[0] === 0x38 && // '8' buffer[1] === 0x42 && // 'B' buffer[2] === 0x50 && // 'P' buffer[3] === 0x53 // 'S' } catch { return false } } /** * 从文件扩展名判断是否为PSD相关格式 * @param filename 文件名 * @returns 是否为PSD相关文件(支持.psd、.tif、.tiff、.psb) */ export function hasPsdExtension(filename: string): boolean { const lowerName = filename.toLowerCase() return lowerName.endsWith('.psd') || lowerName.endsWith('.tif') || lowerName.endsWith('.tiff') || lowerName.endsWith('.psb') }