Spaces:
Sleeping
Sleeping
Merge pull request #12 from huggingface/fix-video-paths
Browse files- backend/server.py +11 -2
- ui/static/app.js +25 -8
backend/server.py
CHANGED
|
@@ -20,11 +20,20 @@ import argparse
|
|
| 20 |
import json
|
| 21 |
import mimetypes
|
| 22 |
import os
|
|
|
|
| 23 |
import sys
|
| 24 |
import urllib.parse
|
| 25 |
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 26 |
from pathlib import Path
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
REPO_ROOT = Path(__file__).resolve().parent.parent
|
| 29 |
INDEXING_DIR = REPO_ROOT / "indexing"
|
| 30 |
UI_STATIC_DIR = REPO_ROOT / "ui" / "static"
|
|
@@ -55,9 +64,9 @@ def _load_allowed_paths() -> set[str]:
|
|
| 55 |
if isinstance(data, list):
|
| 56 |
for entry in data:
|
| 57 |
if isinstance(entry, str):
|
| 58 |
-
allowed.add(entry)
|
| 59 |
elif isinstance(entry, dict) and "path" in entry:
|
| 60 |
-
allowed.add(entry["path"])
|
| 61 |
return allowed
|
| 62 |
|
| 63 |
|
|
|
|
| 20 |
import json
|
| 21 |
import mimetypes
|
| 22 |
import os
|
| 23 |
+
import re
|
| 24 |
import sys
|
| 25 |
import urllib.parse
|
| 26 |
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 27 |
from pathlib import Path
|
| 28 |
|
| 29 |
+
# Video meta entries are stored as "<path> @ <t>s"; strip the timestamp so the
|
| 30 |
+
# allow-list contains the underlying file path.
|
| 31 |
+
_VIDEO_FRAME_SUFFIX = re.compile(r" @ \d+(?:\.\d+)?s$")
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _strip_frame_suffix(entry: str) -> str:
|
| 35 |
+
return _VIDEO_FRAME_SUFFIX.sub("", entry)
|
| 36 |
+
|
| 37 |
REPO_ROOT = Path(__file__).resolve().parent.parent
|
| 38 |
INDEXING_DIR = REPO_ROOT / "indexing"
|
| 39 |
UI_STATIC_DIR = REPO_ROOT / "ui" / "static"
|
|
|
|
| 64 |
if isinstance(data, list):
|
| 65 |
for entry in data:
|
| 66 |
if isinstance(entry, str):
|
| 67 |
+
allowed.add(_strip_frame_suffix(entry))
|
| 68 |
elif isinstance(entry, dict) and "path" in entry:
|
| 69 |
+
allowed.add(_strip_frame_suffix(entry["path"]))
|
| 70 |
return allowed
|
| 71 |
|
| 72 |
|
ui/static/app.js
CHANGED
|
@@ -214,41 +214,58 @@ function makeCard(hit, kind, modality, dark) {
|
|
| 214 |
const card = document.createElement("div");
|
| 215 |
card.className = "card" + (dark ? " dark" : "");
|
| 216 |
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
if (kind === "image") {
|
| 222 |
const thumb = document.createElement("a");
|
| 223 |
thumb.className = "thumb";
|
| 224 |
-
thumb.href = fileUrl(
|
| 225 |
thumb.target = "_blank";
|
| 226 |
thumb.rel = "noopener";
|
| 227 |
const img = document.createElement("img");
|
| 228 |
img.loading = "lazy";
|
| 229 |
img.alt = name;
|
| 230 |
img.addEventListener("load", () => img.classList.add("loaded"));
|
| 231 |
-
img.src = fileUrl(
|
| 232 |
thumb.appendChild(img);
|
| 233 |
card.appendChild(thumb);
|
| 234 |
} else if (kind === "video") {
|
| 235 |
const thumb = document.createElement("a");
|
| 236 |
thumb.className = "thumb";
|
| 237 |
-
|
|
|
|
| 238 |
thumb.target = "_blank";
|
| 239 |
thumb.rel = "noopener";
|
| 240 |
const v = document.createElement("video");
|
| 241 |
v.muted = true;
|
| 242 |
v.preload = "metadata";
|
| 243 |
v.addEventListener("loadeddata", () => v.classList.add("loaded"));
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
thumb.appendChild(v);
|
| 246 |
card.appendChild(thumb);
|
| 247 |
}
|
| 248 |
|
| 249 |
const nameEl = document.createElement("a");
|
| 250 |
nameEl.className = "card-name";
|
| 251 |
-
nameEl.href = fileUrl(
|
| 252 |
nameEl.target = "_blank";
|
| 253 |
nameEl.rel = "noopener";
|
| 254 |
nameEl.style.color = "inherit";
|
|
|
|
| 214 |
const card = document.createElement("div");
|
| 215 |
card.className = "card" + (dark ? " dark" : "");
|
| 216 |
|
| 217 |
+
// Video meta entries are stored as "<path> @ <t>s" — split them so the
|
| 218 |
+
// file request uses the real path and the player can seek to that frame.
|
| 219 |
+
const rawPath = hit.path;
|
| 220 |
+
let filePath = rawPath;
|
| 221 |
+
let timestamp = null;
|
| 222 |
+
if (kind === "video") {
|
| 223 |
+
const m = rawPath.match(/^(.*) @ (\d+(?:\.\d+)?)s$/);
|
| 224 |
+
if (m) {
|
| 225 |
+
filePath = m[1];
|
| 226 |
+
timestamp = parseFloat(m[2]);
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
const name = rawPath.split("/").pop();
|
| 230 |
+
const parent = filePath.replace(/\/[^/]+$/, "");
|
| 231 |
|
| 232 |
if (kind === "image") {
|
| 233 |
const thumb = document.createElement("a");
|
| 234 |
thumb.className = "thumb";
|
| 235 |
+
thumb.href = fileUrl(filePath);
|
| 236 |
thumb.target = "_blank";
|
| 237 |
thumb.rel = "noopener";
|
| 238 |
const img = document.createElement("img");
|
| 239 |
img.loading = "lazy";
|
| 240 |
img.alt = name;
|
| 241 |
img.addEventListener("load", () => img.classList.add("loaded"));
|
| 242 |
+
img.src = fileUrl(filePath);
|
| 243 |
thumb.appendChild(img);
|
| 244 |
card.appendChild(thumb);
|
| 245 |
} else if (kind === "video") {
|
| 246 |
const thumb = document.createElement("a");
|
| 247 |
thumb.className = "thumb";
|
| 248 |
+
const frag = timestamp != null ? `#t=${timestamp}` : "";
|
| 249 |
+
thumb.href = fileUrl(filePath) + frag;
|
| 250 |
thumb.target = "_blank";
|
| 251 |
thumb.rel = "noopener";
|
| 252 |
const v = document.createElement("video");
|
| 253 |
v.muted = true;
|
| 254 |
v.preload = "metadata";
|
| 255 |
v.addEventListener("loadeddata", () => v.classList.add("loaded"));
|
| 256 |
+
if (timestamp != null) {
|
| 257 |
+
v.addEventListener("loadedmetadata", () => {
|
| 258 |
+
try { v.currentTime = timestamp; } catch (_) {}
|
| 259 |
+
});
|
| 260 |
+
}
|
| 261 |
+
v.src = fileUrl(filePath) + frag;
|
| 262 |
thumb.appendChild(v);
|
| 263 |
card.appendChild(thumb);
|
| 264 |
}
|
| 265 |
|
| 266 |
const nameEl = document.createElement("a");
|
| 267 |
nameEl.className = "card-name";
|
| 268 |
+
nameEl.href = fileUrl(filePath);
|
| 269 |
nameEl.target = "_blank";
|
| 270 |
nameEl.rel = "noopener";
|
| 271 |
nameEl.style.color = "inherit";
|