Spaces:
Sleeping
Sleeping
fix: browser compatibility
Browse files- demo.html +25 -0
- utils/video.py +14 -4
demo.html
CHANGED
|
@@ -166,11 +166,18 @@ async function executeMission() {
|
|
| 166 |
videoForm.append("prompt", mission);
|
| 167 |
videoForm.append("detector", detector);
|
| 168 |
|
|
|
|
| 169 |
const response = await fetch(PROCESS_VIDEO_URL, {
|
| 170 |
method: "POST",
|
| 171 |
body: videoForm
|
| 172 |
});
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
if (!response.ok) {
|
| 175 |
let errorDetail = `Request failed (${response.status})`;
|
| 176 |
try {
|
|
@@ -183,10 +190,21 @@ async function executeMission() {
|
|
| 183 |
}
|
| 184 |
|
| 185 |
const videoBlob = await response.blob();
|
|
|
|
| 186 |
const videoUrl = URL.createObjectURL(videoBlob);
|
| 187 |
const videoEl = document.getElementById("processedVideo");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
videoEl.src = videoUrl;
|
| 189 |
videoEl.load();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
|
| 191 |
statusEl.textContent = "Generating summary...";
|
| 192 |
|
|
@@ -195,6 +213,7 @@ async function executeMission() {
|
|
| 195 |
summaryForm.append("prompt", mission);
|
| 196 |
summaryForm.append("detector", detector);
|
| 197 |
|
|
|
|
| 198 |
const summaryResponse = await fetch(SUMMARY_URL, {
|
| 199 |
method: "POST",
|
| 200 |
body: summaryForm
|
|
@@ -208,6 +227,12 @@ async function executeMission() {
|
|
| 208 |
throw new Error(errorDetail);
|
| 209 |
}
|
| 210 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
const summaryJson = await summaryResponse.json();
|
| 212 |
const summaryText = summaryJson.mission_summary || "No summary returned.";
|
| 213 |
summaryEl.textContent = summaryText;
|
|
|
|
| 166 |
videoForm.append("prompt", mission);
|
| 167 |
videoForm.append("detector", detector);
|
| 168 |
|
| 169 |
+
console.log("[process_video] submitting request", { detector, missionLength: mission.length, fileSize: videoFile.size });
|
| 170 |
const response = await fetch(PROCESS_VIDEO_URL, {
|
| 171 |
method: "POST",
|
| 172 |
body: videoForm
|
| 173 |
});
|
| 174 |
|
| 175 |
+
console.log(
|
| 176 |
+
"[process_video] response meta",
|
| 177 |
+
response.status,
|
| 178 |
+
response.statusText,
|
| 179 |
+
{ contentLength: response.headers.get("content-length"), contentType: response.headers.get("content-type") }
|
| 180 |
+
);
|
| 181 |
if (!response.ok) {
|
| 182 |
let errorDetail = `Request failed (${response.status})`;
|
| 183 |
try {
|
|
|
|
| 190 |
}
|
| 191 |
|
| 192 |
const videoBlob = await response.blob();
|
| 193 |
+
console.log("[process_video] blob size", videoBlob.size, videoBlob.type);
|
| 194 |
const videoUrl = URL.createObjectURL(videoBlob);
|
| 195 |
const videoEl = document.getElementById("processedVideo");
|
| 196 |
+
videoEl.addEventListener("loadeddata", () => {
|
| 197 |
+
console.log("[process_video] video loadeddata event", videoEl.readyState);
|
| 198 |
+
}, { once: true });
|
| 199 |
+
videoEl.addEventListener("error", (event) => {
|
| 200 |
+
console.error("[process_video] video error", videoEl.error, event);
|
| 201 |
+
}, { once: true });
|
| 202 |
videoEl.src = videoUrl;
|
| 203 |
videoEl.load();
|
| 204 |
+
console.log("[process_video] video element readyState after load()", videoEl.readyState);
|
| 205 |
+
if (videoEl.error) {
|
| 206 |
+
console.error("[process_video] immediate video error", videoEl.error);
|
| 207 |
+
}
|
| 208 |
|
| 209 |
statusEl.textContent = "Generating summary...";
|
| 210 |
|
|
|
|
| 213 |
summaryForm.append("prompt", mission);
|
| 214 |
summaryForm.append("detector", detector);
|
| 215 |
|
| 216 |
+
console.log("[mission_summary] submitting request", { detector, missionLength: mission.length, fileSize: videoFile.size });
|
| 217 |
const summaryResponse = await fetch(SUMMARY_URL, {
|
| 218 |
method: "POST",
|
| 219 |
body: summaryForm
|
|
|
|
| 227 |
throw new Error(errorDetail);
|
| 228 |
}
|
| 229 |
|
| 230 |
+
console.log(
|
| 231 |
+
"[mission_summary] response meta",
|
| 232 |
+
summaryResponse.status,
|
| 233 |
+
summaryResponse.statusText,
|
| 234 |
+
{ contentLength: summaryResponse.headers.get("content-length"), contentType: summaryResponse.headers.get("content-type") }
|
| 235 |
+
);
|
| 236 |
const summaryJson = await summaryResponse.json();
|
| 237 |
const summaryText = summaryJson.mission_summary || "No summary returned.";
|
| 238 |
summaryEl.textContent = summaryText;
|
utils/video.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
from typing import List, Tuple
|
| 2 |
|
| 3 |
import cv2
|
|
@@ -30,10 +31,19 @@ def extract_frames(video_path: str) -> Tuple[List[np.ndarray], float, int, int]:
|
|
| 30 |
def write_video(frames: List[np.ndarray], output_path: str, fps: float, width: int, height: int) -> None:
|
| 31 |
if not frames:
|
| 32 |
raise ValueError("No frames available for writing.")
|
| 33 |
-
|
| 34 |
-
writer =
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
for frame in frames:
|
| 39 |
writer.write(frame)
|
|
|
|
| 1 |
+
import logging
|
| 2 |
from typing import List, Tuple
|
| 3 |
|
| 4 |
import cv2
|
|
|
|
| 31 |
def write_video(frames: List[np.ndarray], output_path: str, fps: float, width: int, height: int) -> None:
|
| 32 |
if not frames:
|
| 33 |
raise ValueError("No frames available for writing.")
|
| 34 |
+
candidates = ("avc1", "mp4v")
|
| 35 |
+
writer = None
|
| 36 |
+
for codec in candidates:
|
| 37 |
+
fourcc = cv2.VideoWriter_fourcc(*codec)
|
| 38 |
+
writer = cv2.VideoWriter(output_path, fourcc, fps or 1.0, (width, height))
|
| 39 |
+
if writer.isOpened():
|
| 40 |
+
if codec != candidates[0]:
|
| 41 |
+
logging.warning("Falling back to %s codec for VideoWriter.", codec)
|
| 42 |
+
break
|
| 43 |
+
writer.release()
|
| 44 |
+
writer = None
|
| 45 |
+
if writer is None or not writer.isOpened():
|
| 46 |
+
raise ValueError("Failed to open VideoWriter with a supported codec.")
|
| 47 |
|
| 48 |
for frame in frames:
|
| 49 |
writer.write(frame)
|