Files

77 lines
2.0 KiB
JavaScript

export function buildThumbnailCandidateTimes(durationSeconds) {
const duration = Number(durationSeconds);
if (!Number.isFinite(duration) || duration <= 0) {
return [0.15];
}
const upperBound = Math.max(0.05, duration - 0.05);
const baseCandidates = [0.15, 0.5, 1, 2, 3.5, 5];
const candidates = [];
for (const candidate of baseCandidates) {
const clamped = Math.max(0, Math.min(candidate, upperBound));
if (candidates.length === 0 || Math.abs(candidates[candidates.length - 1] - clamped) > 0.08) {
candidates.push(Number(clamped.toFixed(3)));
}
}
if (upperBound > 0.25) {
const fallback = Number(upperBound.toFixed(3));
if (Math.abs(candidates[candidates.length - 1] - fallback) > 0.08) {
candidates.push(fallback);
}
}
return candidates;
}
export function analyzeFrameLuma(data, options = {}) {
const bytes = data instanceof Uint8ClampedArray ? data : new Uint8ClampedArray(data ?? []);
const pixelCount = Math.floor(bytes.length / 4);
if (pixelCount === 0) {
return {
sampleCount: 0,
averageLuma: 0,
darkRatio: 1,
isBlackFrame: true
};
}
const {
darknessThreshold = 18,
maxDarkRatio = 0.985,
averageThreshold = 22,
stride = 16
} = options;
let samples = 0;
let darkSamples = 0;
let lumaTotal = 0;
for (let index = 0; index < pixelCount; index += stride) {
const offset = index * 4;
const red = bytes[offset];
const green = bytes[offset + 1];
const blue = bytes[offset + 2];
const alpha = bytes[offset + 3];
const luma = alpha === 0 ? 0 : 0.2126 * red + 0.7152 * green + 0.0722 * blue;
lumaTotal += luma;
samples += 1;
if (luma <= darknessThreshold) {
darkSamples += 1;
}
}
const averageLuma = samples > 0 ? lumaTotal / samples : 0;
const darkRatio = samples > 0 ? darkSamples / samples : 1;
const isBlackFrame = averageLuma <= averageThreshold && darkRatio >= maxDarkRatio;
return {
sampleCount: samples,
averageLuma,
darkRatio,
isBlackFrame
};
}