feat: 智能选择非黑色视频封面帧
This commit is contained in:
86
main.js
86
main.js
@@ -3790,6 +3790,71 @@ var fillAudioPreviewArtwork = (app, file, cover) => {
|
||||
img.alt = file.name;
|
||||
});
|
||||
};
|
||||
var waitForSeek = (video, time) => new Promise((resolve) => {
|
||||
const onSeeked = () => {
|
||||
video.removeEventListener("seeked", onSeeked);
|
||||
resolve();
|
||||
};
|
||||
video.addEventListener("seeked", onSeeked, { once: true });
|
||||
try {
|
||||
video.currentTime = time;
|
||||
} catch (error) {
|
||||
video.removeEventListener("seeked", onSeeked);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
var isCanvasFrameMostlyBlack = (ctx, width, height) => {
|
||||
const sampleWidth = Math.max(8, Math.min(64, width));
|
||||
const sampleHeight = Math.max(8, Math.min(64, height));
|
||||
const imageData = ctx.getImageData(0, 0, sampleWidth, sampleHeight).data;
|
||||
let brightPixels = 0;
|
||||
let totalPixels = 0;
|
||||
for (let i = 0; i < imageData.length; i += 4) {
|
||||
const luminance = imageData[i] * 0.2126 + imageData[i + 1] * 0.7152 + imageData[i + 2] * 0.0722;
|
||||
if (luminance >= 20)
|
||||
brightPixels++;
|
||||
totalPixels++;
|
||||
}
|
||||
if (!totalPixels)
|
||||
return true;
|
||||
return brightPixels / totalPixels < 0.02;
|
||||
};
|
||||
var pickNonBlackVideoCoverTime = (video) => __async(null, null, function* () {
|
||||
const duration = Number.isFinite(video.duration) ? video.duration : 0;
|
||||
if (duration <= 0.1)
|
||||
return 0;
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
||||
if (!ctx)
|
||||
return 0;
|
||||
const checkpoints = [
|
||||
0.02,
|
||||
0.06,
|
||||
0.12,
|
||||
0.2,
|
||||
0.35,
|
||||
0.5,
|
||||
0.7,
|
||||
0.85
|
||||
];
|
||||
const tried = /* @__PURE__ */ new Set();
|
||||
for (const ratio of checkpoints) {
|
||||
const target = Math.min(Math.max(duration * ratio, 0), Math.max(duration - 0.05, 0));
|
||||
const bucket = Math.round(target * 100);
|
||||
if (tried.has(bucket))
|
||||
continue;
|
||||
tried.add(bucket);
|
||||
yield waitForSeek(video, target);
|
||||
if (video.readyState < 2)
|
||||
continue;
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
if (!isCanvasFrameMostlyBlack(ctx, canvas.width, canvas.height))
|
||||
return target;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
var appendAudioPreview = (app, figure, file, settings) => {
|
||||
figure.addClass("img-gallery-audio-item");
|
||||
const audioCard = figure.createEl("div", { cls: "img-gallery-audio-card" });
|
||||
@@ -3821,13 +3886,32 @@ var appendPreviewMedia = (app, figure, file, settings) => {
|
||||
"object-position": "center center",
|
||||
display: "block"
|
||||
});
|
||||
let coverTime = 0;
|
||||
let coverInitialized = false;
|
||||
const ensureVideoCover = () => __async(null, null, function* () {
|
||||
if (coverInitialized)
|
||||
return;
|
||||
coverInitialized = true;
|
||||
video.pause();
|
||||
const pickedTime = yield pickNonBlackVideoCoverTime(video);
|
||||
coverTime = Number.isFinite(pickedTime) ? pickedTime : 0;
|
||||
yield waitForSeek(video, coverTime);
|
||||
video.pause();
|
||||
});
|
||||
video.addEventListener("loadedmetadata", () => {
|
||||
void ensureVideoCover();
|
||||
}, { once: true });
|
||||
video.addEventListener("mouseenter", () => {
|
||||
void video.play().catch(() => {
|
||||
});
|
||||
});
|
||||
video.addEventListener("mouseleave", () => {
|
||||
video.pause();
|
||||
video.currentTime = 0;
|
||||
if (coverInitialized) {
|
||||
void waitForSeek(video, coverTime);
|
||||
} else {
|
||||
video.currentTime = 0;
|
||||
}
|
||||
});
|
||||
return video;
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"1.0.0": "0.14.6",
|
||||
"1.1.0": "1.1.8",
|
||||
"1.1.1": "1.1.8",
|
||||
"2.0.0": "1.1.8"
|
||||
"2.0.0": "1.1.8",
|
||||
"2.0.1": "2.0.1"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user