B. 二分定位 + 邻域采样(我推荐) 把配置的语义从"这就是要生成的 10 档"改成"允许搜索的参数梯度"(比如分辨率梯度、帧率梯度、质量梯度三张表)。运行时: 1. 用每个组合只编 2 帧的方式粗估体积(很便宜); 2. 靠单调性做二分,几轮就找到"体积刚好不超过目标"的参数点; 3. 在这个点周围取 10 个组合(略高、略低各几个),完整生成。 成本大概是十几次估算编码 + 10 档完整编码,和现在基本持平甚至更省(估算只编 2 帧)。效果是候选真正围绕你输入的目标分布——比如你填 30 KB,出来的就是 22、26、29、31、35 KB 这种,而不是 5 KB 和 300 KB 各一个。 代价是你现在这套配置格式要推翻重做——从"精确档位表"变成"参数范围表"
78 lines
3.6 KiB
JavaScript
78 lines
3.6 KiB
JavaScript
'use strict';
|
|
|
|
const crypto = require('crypto');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
function orderCurrentTargetLast(targets, currentSourcePath) {
|
|
if (!currentSourcePath) return [...targets];
|
|
const current = path.resolve(currentSourcePath);
|
|
return [...targets].sort((left, right) => {
|
|
const leftIsCurrent = path.resolve(left.sourcePath) === current;
|
|
const rightIsCurrent = path.resolve(right.sourcePath) === current;
|
|
return Number(leftIsCurrent) - Number(rightIsCurrent);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Apply one rendered frame to explicitly selected Eagle items.
|
|
* Each item gets an independent temporary file because Eagle may consume or
|
|
* defer reading the file passed to setCustomThumbnail.
|
|
*/
|
|
async function applyThumbnailTargets(options) {
|
|
const targets = options.targets || [];
|
|
// 渲染结果为空时立刻报清楚的原因,否则会在写临时文件时才抛出难懂的参数类型错误。
|
|
if (!options.rendered || !Buffer.isBuffer(options.rendered.buffer)) throw new Error('缩略图渲染结果为空');
|
|
const settleDelayMs = options.settleDelayMs === undefined ? 800 : options.settleDelayMs;
|
|
const cleanupDelayMs = options.cleanupDelayMs === undefined ? 1200 : options.cleanupDelayMs;
|
|
const retryAttempts = options.retryAttempts === undefined ? 3 : options.retryAttempts;
|
|
const delay = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
const tempPaths = [];
|
|
let success = 0;
|
|
let skipped = Number(options.initialSkipped) || 0;
|
|
let failed = 0;
|
|
for (let index = 0; index < targets.length; index++) {
|
|
if (options.onProgress) options.onProgress(index + 1, targets.length);
|
|
let itemTempPath = null;
|
|
try {
|
|
const target = targets[index];
|
|
const fresh = await options.getItem(target.sourcePath);
|
|
if (fresh.data.customThumbnail === true && target.allowOverwrite !== true) { skipped++; continue; }
|
|
itemTempPath = path.join(os.tmpdir(), 'spineviewer-group-' + fresh.id + '-' + crypto.randomBytes(12).toString('hex') + options.rendered.extension);
|
|
fs.writeFileSync(itemTempPath, options.rendered.buffer);
|
|
tempPaths.push(itemTempPath);
|
|
let applied = false;
|
|
let lastError = null;
|
|
for (let attempt = 0; attempt < retryAttempts && !applied; attempt++) {
|
|
try {
|
|
const accepted = await options.setThumbnail(fresh.id, itemTempPath);
|
|
if (accepted !== true) throw new Error(options.notConfirmedMessage || 'Thumbnail update was rejected');
|
|
applied = await options.waitForApplied(target.sourcePath);
|
|
if (!applied) throw new Error(options.notConfirmedMessage || 'Thumbnail update was not confirmed');
|
|
} catch (error) {
|
|
lastError = error;
|
|
if (attempt < retryAttempts - 1) await delay(settleDelayMs);
|
|
}
|
|
}
|
|
if (!applied) throw lastError || new Error(options.notConfirmedMessage || 'Thumbnail update was not confirmed');
|
|
// The local HTTP API can resolve before Eagle has finished consuming the
|
|
// supplied file. Keep every file alive and serialize requests with a short
|
|
// settling interval so a later request cannot replace an earlier queued job.
|
|
await delay(settleDelayMs);
|
|
success++;
|
|
if (options.onSuccess) options.onSuccess(fresh);
|
|
} catch (error) {
|
|
failed++;
|
|
if (options.onError) options.onError(error);
|
|
} finally { /* temporary inputs are retained until the whole queue settles */ }
|
|
}
|
|
await delay(cleanupDelayMs);
|
|
for (const tempPath of tempPaths) {
|
|
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) { /* ignore */ }
|
|
}
|
|
return { success, skipped, failed };
|
|
}
|
|
|
|
module.exports = { applyThumbnailTargets, orderCurrentTargetLast };
|