支持动图下载和预览
This commit is contained in:
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user