(inputPath, filename)
| 137 | |
| 138 | // 生成缩略图 (with better error recovery) |
| 139 | async function generateThumbnails(inputPath, filename) { |
| 140 | const baseName = path.parse(filename).name; |
| 141 | |
| 142 | try { |
| 143 | const image = sharp(inputPath); |
| 144 | const metadata = await image.metadata(); |
| 145 | |
| 146 | // Skip if image is already smaller than thumbnail size |
| 147 | if (metadata.width <= config.sizes.thumbnail.width && |
| 148 | metadata.height <= config.sizes.thumbnail.height) { |
| 149 | BuildLogger.log(`⏭️ 跳过缩略图生成: ${filename} (已足够小)`); |
| 150 | return { success: true, skipped: true }; |
| 151 | } |
| 152 | |
| 153 | // Generate thumbnail |
| 154 | const thumbnailPath = path.join(config.outputDir, `${baseName}-thumb.jpg`); |
| 155 | |
| 156 | // Check if thumbnail already exists and is newer |
| 157 | if (fs.existsSync(thumbnailPath)) { |
| 158 | const inputStats = await stat(inputPath); |
| 159 | const thumbStats = await stat(thumbnailPath); |
| 160 | if (thumbStats.mtime > inputStats.mtime) { |
| 161 | BuildLogger.log(`⏭️ 跳过已存在的缩略图: ${path.basename(thumbnailPath)}`); |
| 162 | return { success: true, skipped: true }; |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | await sharp(inputPath) |
| 167 | .resize(config.sizes.thumbnail.width, config.sizes.thumbnail.height, { |
| 168 | fit: 'cover', |
| 169 | position: 'center' |
| 170 | }) |
| 171 | .jpeg({ quality: 80, progressive: true }) |
| 172 | .toFile(thumbnailPath); |
| 173 | |
| 174 | BuildLogger.success(' 缩略图生成: ${path.basename(thumbnailPath)}'); |
| 175 | return { success: true, skipped: false }; |
| 176 | } catch (error) { |
| 177 | console.error(`❌ 缩略图生成失败: ${filename}`, error.message); |
| 178 | return { success: false, error: error.message }; |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | // 生成图片清单 |
| 183 | function generateManifest() { |
no test coverage detected