支持动图下载和预览

This commit is contained in:
2025-10-13 15:43:18 +08:00
parent e85f959fa6
commit 5be8ae9520
18 changed files with 909 additions and 32 deletions
+80
View File
@@ -0,0 +1,80 @@
const fs = require('fs');
const path = require('path');
const fsExtra = require('fs-extra');
// These dependencies are expected in project dependencies
const AdmZip = require('adm-zip');
const jpeg = require('jpeg-js');
const GIFEncoder = require('gifencoder');
/**
* Generate an animated GIF preview from a Pixiv ugoira ZIP and frame metadata.
* - Extracts frames from ZIP into a temp directory
* - Encodes frames into GIF honoring per-frame delays
* - Writes GIF to outPath
*
* @param {string} zipPath - Path to the downloaded ugoira ZIP file
* @param {Array<{file:string, delay:number}>} frames - Frame metadata from Pixiv API
* @param {string} outPath - Destination path for the generated GIF (e.g., preview.gif)
*/
async function generatePreviewGifFromUgoira(zipPath, frames, outPath) {
if (!zipPath || !Array.isArray(frames) || frames.length === 0 || !outPath) {
throw new Error('Invalid parameters for generating ugoira preview GIF');
}
const tmpDir = path.join(path.dirname(outPath), '.ugoira_tmp');
await fsExtra.ensureDir(tmpDir);
try {
// Extract all frames
const zip = new AdmZip(zipPath);
zip.extractAllTo(tmpDir, true);
// Determine size from first frame
const firstFramePath = path.join(tmpDir, frames[0].file);
const firstBuf = fs.readFileSync(firstFramePath);
const firstDecoded = jpeg.decode(firstBuf, { useTArray: true });
const width = firstDecoded.width;
const height = firstDecoded.height;
// Initialize encoder and output stream
const encoder = new GIFEncoder(width, height);
const ws = fs.createWriteStream(outPath);
encoder.createReadStream().pipe(ws);
encoder.start();
encoder.setRepeat(0); // loop forever
encoder.setQuality(10);
// Add frames honoring per-frame delay
for (const f of frames) {
const framePath = path.join(tmpDir, f.file);
const buf = fs.readFileSync(framePath);
const decoded = jpeg.decode(buf, { useTArray: true });
// Ensure dimensions match; if not, skip or resize (skip for simplicity)
if (decoded.width !== width || decoded.height !== height) {
// Skip mismatched frames to keep encoder stable
continue;
}
encoder.setDelay(typeof f.delay === 'number' ? f.delay : 0);
encoder.addFrame(decoded.data);
}
encoder.finish();
await new Promise((resolve, reject) => {
ws.on('finish', resolve);
ws.on('error', reject);
});
} finally {
// Cleanup extracted frames
try { await fsExtra.remove(tmpDir); } catch (_) {}
}
}
module.exports = {
generatePreviewGifFromUgoira,
};