80 lines
2.6 KiB
JavaScript
80 lines
2.6 KiB
JavaScript
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,
|
|
}; |