feat(backend): add video scene preview generation and endpoint
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m52s

- Register a new route `GET /d/{boxID}/scene/{fileID}` to serve video scene previews.
- Implement the `VideoScenesPreview` handler to serve existing previews or generate them on-demand.
- Add helper functions to analyze video frames (e.g., luma calculation to filter out dark frames) and render the final scene thumbnail.
- Update the `fileView` struct to include scene URL and status fields.
This commit is contained in:
2026-06-05 10:42:30 +03:00
parent 2eba04b9da
commit f9755fa98f
9 changed files with 552 additions and 32 deletions

View File

@@ -16,6 +16,7 @@
sourceURL: preview.dataset.sourceUrl || "",
downloadURL: preview.dataset.downloadUrl || "",
iconURL: preview.dataset.iconUrl || "",
sceneURL: preview.dataset.sceneUrl || "",
activeMode: "",
defaultMode: "default",
pendingMode: "",
@@ -24,6 +25,7 @@
rawLoaded: false,
prismLoaded: false,
renderLoaded: false,
sceneLoaded: false,
renderFullscreenFallback: false,
confirmedLargeModes: {},
tabs: []
@@ -35,6 +37,7 @@
defaultPane: preview.querySelector("[data-default-preview]"),
imagePane: preview.querySelector("[data-image-preview]"),
videoPane: preview.querySelector("[data-video-preview]"),
videoScenesPane: preview.querySelector("[data-video-scenes-preview]"),
browserAudioPane: preview.querySelector("[data-browser-audio-preview]"),
rawPane: preview.querySelector("[data-raw-preview]"),
rawOutput: preview.querySelector("[data-raw-output]"),
@@ -90,6 +93,9 @@
if (type.isVideo) {
tabs.push({ mode: "video", label: "Video Preview" });
if (state.sceneURL && els.videoScenesPane) {
tabs.push({ mode: "scenes", label: "Scenes Preview" });
}
return tabs;
}
@@ -181,6 +187,9 @@
show(els.imagePane);
} else if (mode === "video") {
show(els.videoPane);
} else if (mode === "scenes") {
show(els.videoScenesPane);
ensureScenesPreview();
} else if (mode === "browser-audio") {
show(els.browserAudioPane);
} else if (mode === "raw") {
@@ -403,6 +412,7 @@
hide(els.defaultPane);
hide(els.imagePane);
hide(els.videoPane);
hide(els.videoScenesPane);
hide(els.browserAudioPane);
hide(els.rawPane);
hide(els.codePane);
@@ -498,6 +508,7 @@
"default": "Default",
"image": "Image preview",
"video": "Video preview",
"scenes": "Scenes preview",
"browser-audio": "Browser preview",
"raw": "Raw preview",
"code": "Code preview",
@@ -506,6 +517,18 @@
return labels[mode] || "Preview";
}
function ensureScenesPreview() {
if (state.sceneLoaded || !els.videoScenesPane) {
return;
}
var src = els.videoScenesPane.dataset.sceneSrc || state.sceneURL;
if (!src) {
return;
}
els.videoScenesPane.src = src;
state.sceneLoaded = true;
}
function loadPrism() {
if (window.Prism) {
return Promise.resolve();