This commit is contained in:
2025-09-22 11:07:28 -04:00
parent 47f631fedb
commit aa5ad8327e
9 changed files with 821 additions and 620 deletions

View File

@@ -8,8 +8,8 @@
background: #181a20; background: #181a20;
} }
/* Video.js player */ /* Video.js player styles */
.video-js-mod { .video-player-fullscreen {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@@ -17,98 +17,105 @@
height: 100vh; height: 100vh;
object-fit: contain; object-fit: contain;
background: #000; background: #000;
} }
.vjs-tech { .video-tech {
object-fit: contain; object-fit: contain;
} }
/* Main app layout */ /* Main app container */
.app-container { .app-container {
display: flex;
flex-direction: column;
height: 100vh; height: 100vh;
width: 100vw; width: 95vw;
margin: 0; max-width: 95vw;
margin: 0 auto;
padding: 0; padding: 0;
gap: 12px; gap: 0;
/* background: #181a20; */
} }
/* Controls section */
.flex-group { .controls-section {
display: flex; width: 100%;
flex-direction: column; /* or 'row' if you want horizontal grouping */ margin-bottom: 12px;
flex: 1 1 0;
min-width: 0;
}
/* Section containers */
.section-box-horiz {
overflow: visible;
flex-direction: row;
display: flex;
align-items: center;
justify-content: center;
}
/* Section containers */
.section-box {
flex: 0 0 5%;
overflow: visible;
/* background: #23272f; */
/* padding: 0;
box-sizing: border-box;
border-radius: 10px;
margin: 0 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.10); */
display: flex;
align-items: center;
justify-content: center;
}
.timeline-container {
flex: 0 0 24%;
overflow: visible;
background: #20232a;
padding: 0;
box-sizing: border-box;
border-radius: 10px;
margin: 0 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.10);
display: flex;
align-items: center;
justify-content: center;
}
.section-box:last-of-type {
flex: 1 1 68%;
overflow: hidden;
background: #23272f; background: #23272f;
padding: 0;
box-sizing: border-box;
border-radius: 10px; border-radius: 10px;
margin: 0 16px 16px 16px; padding: 16px 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.10); text-align: center;
display: flex; box-sizing: border-box;
align-items: center;
justify-content: center;
} }
/* Responsive tweaks */ /* Control group wrapper */
@media (max-width: 600px) { .control-group {
.app-container { display: inline-block;
gap: 6px; vertical-align: middle;
min-width: 10%;
margin: 0 8px;
} }
.section-box,
.timeline-container, /* Status display section */
.section-box:last-of-type { .status-section {
margin: 0 4px; width: 100%;
border-radius: 6px; margin-bottom: 12px;
}
/* Timeline container */
.timeline-section {
width: 100%;
height: 30vh;
min-height: 200px;
background: #20232a;
border-radius: 10px;
margin: 0 auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.10);
text-align: center;
box-sizing: border-box;
margin-bottom: 12px;
padding: 0; padding: 0;
} }
.date-range-selector {
/* Video player section */
.video-section {
width: 100%;
height: 60vw;
margin-top: 12px;
min-height: 40vw;
background: #23272f;
border-radius: 10px;
margin: 0 auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.10);
text-align: center;
box-sizing: border-box;
margin-bottom: 12px;
padding: 0;
}
/* Date range picker styles */
.date-picker-wrapper {
max-width: 98vw; max-width: 98vw;
padding: 12px 8px; padding: 12px 8px;
border-radius: 8px; border-radius: 8px;
} }
/* Responsive styles */
@media (max-width: 600px) {
.app-container {
gap: 0;
}
.video-section,
.timeline-section {
margin: 0 4px 8px 4px;
border-radius: 6px;
}
.date-picker-wrapper {
max-width: 98vw;
padding: 12px 8px;
border-radius: 8px;
}
.controls-section {
padding: 8px 0;
}
} }

View File

@@ -155,6 +155,8 @@ function App() {
newData["videos"] = newData["videos"].filter( newData["videos"] = newData["videos"].filter(
(vid) => vid["embed_scores"]["score"][1] >= floatValue (vid) => vid["embed_scores"]["score"][1] >= floatValue
); );
newData['threshold'] = floatValue;
console.log(newData['threshold'])
setDataResults(newData); setDataResults(newData);
} }
@@ -209,21 +211,58 @@ function App() {
); );
} }
} }
// Memoize the timeline click handler
const handleTimelineClick = useCallback( const handleTimelineClick = useCallback(
(path, timeoffset) => { (path, timeoffset) => {
if (playerRef.current && playerInstanceRef.current) { if (playerRef.current && playerInstanceRef.current) {
playerInstanceRef.current.src({ const player = playerInstanceRef.current;
console.log("Setting video source:", "api/" + path);
console.log("Target time offset:", timeoffset);
// Clear any existing source first
player.reset();
player.src({
src: "api/" + path, src: "api/" + path,
type: "video/mp4", type: "video/mp4",
}); });
playerInstanceRef.current.on("loadedmetadata", () => {
playerInstanceRef.current.currentTime(timeoffset); player.load();
// Add multiple event listeners for debugging
player.one("loadedmetadata", () => {
console.log("Video metadata loaded");
console.log("Video duration:", player.duration());
player.currentTime(timeoffset);
// Try to play after setting time
const playPromise = player.play();
if (playPromise !== undefined) {
playPromise.then(() => {
console.log("Video started playing");
}).catch(error => {
console.error("Error playing video:", error);
}); });
} }
});
player.one("error", (e) => {
console.error("Video error:", e);
console.error("Player error:", player.error());
});
player.one("loadstart", () => {
console.log("Load started");
});
player.one("canplay", () => {
console.log("Video can start playing");
});
} else {
console.error("Player ref not available");
}
}, },
[] // Empty dependency array since it only uses playerRef []
); );
useEffect(() => { useEffect(() => {
@@ -263,12 +302,8 @@ function App() {
return ( return (
<div className="app-container"> <div className="app-container">
<div <div className="controls-section">
className={`section-box-horiz${ <div className="control-group">
drawerOpen ? " drawer-open" : ""
}`}
>
<div className="flex-group">
<CustomDateRangePicker <CustomDateRangePicker
startDate={startRange} startDate={startRange}
endDate={endRange} endDate={endRange}
@@ -276,8 +311,7 @@ function App() {
setEndRange={setEndRange} setEndRange={setEndRange}
/> />
</div> </div>
<div className="flex-group"> <div className="control-group">
<div style={{ position: "relative", width: "100%" }}>
<input <input
type="text" type="text"
placeholder="Enter query" placeholder="Enter query"
@@ -287,41 +321,36 @@ function App() {
if (e.key === "Enter") handleResubmit(); if (e.key === "Enter") handleResubmit();
}} }}
style={{ style={{
marginLeft: "16px", // padding: "8px",
marginRight: "16px", // borderRadius: "4px",
padding: "8px", // border: "1px solid #343a40",
borderRadius: "4px", // color: "#fff",
border: "1px solid #343a40", // backgroundColor: "#23272f",
color: "#fff", // width: "100%",
backgroundColor: "#23272f", // minWidth: 0,
width: "100%", // boxSizing: "border-box",
minWidth: 0,
boxSizing: "border-box",
fontSize: "1.1em", fontSize: "1.1em",
transition: "width 0.2s", // transition: "width 0.2s",
}} }}
ref={inputRef} ref={inputRef}
size={Math.max(queryText.length, 1)} size={Math.max(queryText.length, 25)}
/> />
</div> </div>
</div>
<div <div
className="flex-group" className="control-group"
style={{ visibility: queryChanged ? "hidden" : "visible" }} style={{ visibility: queryChanged ? "hidden" : "visible" }}
> >
<label <label style={{ color: "#fff" }}>Threshold:</label>
</div>
<div
className="control-group"
style={{ style={{
marginLeft: "8px", display: "inline-block",
marginRight: "8px", verticalAlign: "middle",
color: "#fff", margin: "0 8px",
width: "120px",
visibility: queryChanged ? "hidden" : "visible",
}} }}
>
Threshold:
</label>
</div>
<div
className="flex-group"
style={{ visibility: queryChanged ? "hidden" : "visible" }}
> >
<input <input
type="range" type="range"
@@ -339,15 +368,13 @@ function App() {
/> />
</div> </div>
<div <div
className="flex-group" className="control-group"
style={{ visibility: queryChanged ? "hidden" : "visible" }} style={{ visibility: queryChanged ? "hidden" : "visible" }}
> >
<span style={{ marginLeft: "8px", color: "#fff" }}> <span style={{ color: "#fff" }}>{sliderValue.toFixed(2)}</span>
{sliderValue.toFixed(2)}
</span>
</div> </div>
<div <div
className="flex-group" className="control-group"
style={{ visibility: queryChanged ? "visible" : "hidden" }} style={{ visibility: queryChanged ? "visible" : "hidden" }}
> >
<button <button
@@ -363,18 +390,20 @@ function App() {
</button> </button>
</div> </div>
</div> </div>
<div>
<div className="status-section">
<StatusesDisplayHUD statusMessages={statusMessages} /> <StatusesDisplayHUD statusMessages={statusMessages} />
</div> </div>
<div className="timeline-container"> <div className="timeline-section">
<EmbedTimeline <EmbedTimeline
chartRef={chartRef} chartRef={chartRef}
data_in={dataResults} data_in={dataResults}
onTimelineClick={handleTimelineClick} onTimelineClick={handleTimelineClick}
/> />
</div> </div>
<div className="section-box">
<div className="video-section vjs-16-9 vjs-fluid">
<VideoPlayer <VideoPlayer
videoRef={playerRef} videoRef={playerRef}
playerInstanceRef={playerInstanceRef} playerInstanceRef={playerInstanceRef}

View File

@@ -1,4 +1,4 @@
import React, { useRef, useEffect } from "react"; import React, { useRef, useEffect, useState } from "react";
import ReactECharts from "echarts-for-react"; import ReactECharts from "echarts-for-react";
const EmbedTimeline = React.memo(function EmbedTimeline({ const EmbedTimeline = React.memo(function EmbedTimeline({
@@ -7,12 +7,121 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
onTimelineClick, onTimelineClick,
markerTime, markerTime,
}) { }) {
// Use useRef instead of props/state to avoid re-renders
const zoomed_range = useRef([null, null]);
const lineDataRef = useRef(null);
const isZoomedInRef = useRef(false);
const hasLineSeriesRef = useRef(false);
// --- Early return if loading --- // --- Early return if loading ---
if (!data_in) return <div>Loading....</div>; if (!data_in) return <div>Loading....</div>;
// --- Constants --- // --- Constants ---
const BREAK_GAP = 0; const BREAK_GAP = 0;
console.log("REDRAW"); const ZOOM_THRESHOLD = 4 * 60 * 60 * 1000; // 1 hour in ms
const fetchLineData = async (startTime, endTime) => {
try {
const params = new URLSearchParams();
params.append("start_time", new Date(startTime).toISOString());
params.append("end_time", new Date(endTime).toISOString());
const response = await fetch(
"/api/line_data.json?" + params.toString()
);
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
const data = await response.json();
return data["line_data"].map((d) => [d[0] * 1000, d[1]]);
} catch (error) {
console.error("Failed to fetch line data:", error);
return null;
}
};
const addLineToChart = (chart, lineData) => {
let vs = chart.getOption().series;
var ser_mod = vs[2];
ser_mod.data = fillNulls(lineData).map((d) => [
virtualTime(new Date(d[0])),
d[1],
]);
chart.setOption({
series: [{}, {}, ser_mod],
});
};
const removeLineFromChart = (chart) => {
let vs = chart.getOption().series;
var ser_mod = vs[2];
ser_mod.data = [];
chart.setOption({
series: [{}, {}, ser_mod],
});
};
const handleDataZoom = async (params, chart) => {
if (!params || !chart) return;
console.log(zoomed_range.current);
let plot_start_time = zoomed_range.current[0];
let plot_end_time = zoomed_range.current[1];
const option = chart.getOption();
const dataZoom = option.dataZoom?.[0];
if (
dataZoom &&
dataZoom.startValue !== undefined &&
dataZoom.endValue !== undefined
) {
const zoomRange = dataZoom.endValue - dataZoom.startValue;
const shouldShowLine = zoomRange < ZOOM_THRESHOLD;
let offset_start = dataZoom.startValue - plot_start_time;
let offset_end = dataZoom.endValue - plot_end_time;
console.log(offset_start, offset_end);
if (offset_start < 0 || offset_end > 0) {
console.log("Do force getting data");
isZoomedInRef.current = false;
}
if (shouldShowLine && !isZoomedInRef.current) {
// console.log("ZOOMED IN");
isZoomedInRef.current = true;
let off = 1000 * 60 * 60;
zoomed_range.current = [
dataZoom.startValue - off,
dataZoom.endValue + off,
];
const startTime = mapVirtualToRealTime(
dataZoom.startValue - off,
breaks,
virtualTime
);
const endTime = mapVirtualToRealTime(
dataZoom.endValue + off,
breaks,
virtualTime
);
const fetchedLineData = await fetchLineData(startTime, endTime);
lineDataRef.current = fetchedLineData;
console.log("Fetched updated data");
// Add line directly to chart without React re-render
addLineToChart(chart, fetchedLineData);
} else if (!shouldShowLine && isZoomedInRef.current) {
console.log("zoomed out");
isZoomedInRef.current = false;
lineDataRef.current = null;
zoomed_range.current = [null, null];
// Remove line directly from chart
removeLineFromChart(chart);
}
}
};
const timeFormatOptions = { const timeFormatOptions = {
withSeconds: { withSeconds: {
@@ -53,9 +162,12 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
// var max_score = Math.max(...item['embed_scores']['score']) // var max_score = Math.max(...item['embed_scores']['score'])
var max_score = item["embed_scores"]["score"][1]; var max_score = item["embed_scores"]["score"][1];
var max_score_time = new Date( var max_score_time = new Date(
start_time.getTime() + 1000 * item["embed_scores"]["score"][3] start_time.getTime() +
1000 * item["embed_scores"]["score"][2]
);
var new_time = new Date(
start_time.getTime() + 1000 * 2 * mean_val
); );
var new_time = new Date(start_time.getTime() + 1000 * 2 * mean_val);
new_data.push([ new_data.push([
new Date(start_time.getTime()), new Date(start_time.getTime()),
new_time, new_time,
@@ -74,9 +186,10 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
}); });
// Remove duplicates and sort // Remove duplicates and sort
return Array.from(new Set(new_data.map(JSON.stringify)), JSON.parse).sort( return Array.from(
(a, b) => new Date(a[0]) - new Date(b[0]) new Set(new_data.map(JSON.stringify)),
); JSON.parse
).sort((a, b) => new Date(a[0]) - new Date(b[0]));
} }
function calculateBreaks(videos) { function calculateBreaks(videos) {
@@ -105,8 +218,8 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
if (i < data.length - 1) { if (i < data.length - 1) {
const curr_time = new Date(data[i][0]).getTime(); const curr_time = new Date(data[i][0]).getTime();
const next_time = new Date(data[i + 1][0]).getTime(); const next_time = new Date(data[i + 1][0]).getTime();
if (next_time - curr_time > 1000) { if (next_time - curr_time > 2000) {
// with_nulls.push([new Date(curr_time + 1), null]); with_nulls.push([new Date(curr_time + 1), null]);
// with_nulls.push([new Date(curr_time + 1), 0]); // with_nulls.push([new Date(curr_time + 1), 0]);
} }
} }
@@ -151,6 +264,7 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
return realMs; return realMs;
} }
const ymin = Math.floor((data_in.threshold ?? 0.0) * 100) / 100;
function buildSeries(item, idx) { function buildSeries(item, idx) {
const data = item.map(function (item, index) { const data = item.map(function (item, index) {
return { return {
@@ -158,14 +272,15 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
}; };
}); });
console.log(data)
return { return {
type: "custom", type: "custom",
renderItem: function (params, api) { renderItem: function (params, api) {
var yValue = api.value(2); var yValue = api.value(2);
var start = api.coord([api.value(0), yValue]); var start = api.coord([api.value(0), yValue]);
var size = api.size([api.value(1) - api.value(0), yValue]); var size = api.size([
api.value(1) - api.value(0),
yValue - ymin,
]);
var style = api.style(); var style = api.style();
var maxTime = api.coord([api.value(3), yValue]); var maxTime = api.coord([api.value(3), yValue]);
return { return {
@@ -184,7 +299,7 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
{ {
type: "circle", type: "circle",
shape: { cx: maxTime[0], cy: maxTime[1], r: 1 }, shape: { cx: maxTime[0], cy: maxTime[1], r: 1 },
style: { fill: "#00F0003F" }, style: { fill: "#FFFF00" },
}, },
], ],
}; };
@@ -201,73 +316,14 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
}; };
} }
function buildInvisibleHitBoxSeries(item, idx) {
const data = item.map(function (item, index) {
return {
value: item,
};
});
return {
type: "custom",
renderItem: function (params, api) {
var yValue = api.value(2);
var start = api.coord([api.value(0), yValue]);
var size = api.size([api.value(1) - api.value(0), yValue]);
var style = api.style();
var maxTime = api.coord([api.value(3), yValue]);
return {
type: "group",
children: [
{
type: "rect",
shape: {
x: start[0],
y: start[1],
width: size[0],
height: size[1],
},
style: { fill: "#00F0003F" },
},
{
type: "circle",
shape: { cx: maxTime[0], cy: maxTime[1], r: 1 },
style: { fill: "#00F0003F" },
},
],
};
},
symbol: "none",
smooth: true,
// large: true,
lineStyle: { normal: { color: "green", width: 1 } },
// data: item.map(d => [d[0], d[1], d[2], d[3]]),
data: data,
// sampling: 'lttb',
triggerLineEvent: true,
z: 11,
};
// return {
// type: 'line',
// symbol: 'none',
// smooth: true,
// large: true,
// lineStyle: { width: 100, opacity: 0 },
// data: item.map(d => [d[0], d[1]]),
// sampling: 'lttb',
// triggerLineEvent: true,
// z: 10
// };
}
function buildBlankSeries() { function buildBlankSeries() {
return { return {
type: "line", type: "line",
symbol: "none", symbol: "none",
lineStyle: { width: 100, opacity: 0 }, lineStyle: { width: 100, opacity: 0 },
data: [], data: [],
smooth: true,
sampling: 'lttb',
z: 4, z: 4,
}; };
} }
@@ -326,11 +382,27 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
] ]
); );
const result = [virtualData]; const result = [virtualData];
const ymax = Math.max(...virtualData.map((d) => d[2])); const ymax = Math.max(...virtualData.map((d) => d[2]));
// --- Series --- // --- Series ---
const seriesNormal = result.map(buildSeries); const seriesNormal = result.map(buildSeries);
// const seriesInvisible = result.map(buildInvisibleHitBoxSeries); // const seriesInvisible = result.map(buildInvisibleHitBoxSeries);
const series_out = [].concat(seriesNormal, buildBlankSeries());
const lineSeries = {
id: "zoomLine",
type: "line",
data: [],
lineStyle: { color: "#00FFFF", width: 1 },
symbol: "none",
z: 20,
animation: false,
triggerLineEvent: true,
xAxisIndex: 0,
connectNulls: false,
yAxisIndex: 0,
};
const series_out = [].concat(seriesNormal, buildBlankSeries(), lineSeries);
// --- Break MarkLines --- // --- Break MarkLines ---
const breakMarkLines = breaks.map((br) => ({ const breakMarkLines = breaks.map((br) => ({
@@ -351,14 +423,21 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
symbol: ["none", "none"], symbol: ["none", "none"],
data: [...(breakMarkLines || []), ...(splitCenterMarkLines || [])], data: [...(breakMarkLines || []), ...(splitCenterMarkLines || [])],
lineStyle: { type: "dashed", color: "#888", width: 2 }, lineStyle: { type: "dashed", color: "#888", width: 2 },
label: { show: true, position: "bottom", color: "#888", fontSize: 10 }, label: {
show: true,
position: "bottom",
color: "#888",
fontSize: 10,
},
}; };
} }
// --- Axis & Chart Option --- // --- Axis & Chart Option ---
const virtual_x_min = virtualData.length > 0 ? virtualData[0][0] : 0; const virtual_x_min =
(virtualData.length > 0 ? virtualData[0][0] : 0) - 100000;
const virtual_x_max = const virtual_x_max =
virtualData.length > 0 ? virtualData[virtualData.length - 1][0] : 1; (virtualData.length > 0 ? virtualData[virtualData.length - 1][0] : 1) +
4100000;
const option = { const option = {
animation: false, animation: false,
@@ -383,14 +462,14 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
xAxisIndex: [0], xAxisIndex: [0],
startValue: virtual_x_min, startValue: virtual_x_min,
endValue: virtual_x_max, endValue: virtual_x_max,
filterMode: 'weakFilter', filterMode: "weakFilter",
}, },
{ {
type: "inside", type: "inside",
xAxisIndex: [0], xAxisIndex: [0],
startValue: virtual_x_min, startValue: virtual_x_min,
endValue: virtual_x_max, endValue: virtual_x_max,
filterMode: 'weakFilter', filterMode: "weakFilter",
}, },
], ],
xAxis: { xAxis: {
@@ -416,13 +495,20 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
range = dz.endValue - dz.startValue; range = dz.endValue - dz.startValue;
} }
} }
const realTime = mapVirtualToRealTime(virtualMs, breaks, virtualTime); const realTime = mapVirtualToRealTime(
virtualMs,
breaks,
virtualTime
);
if (realTime) { if (realTime) {
const useSeconds = range < 5 * 60 * 1000; const useSeconds = range < 5 * 60 * 1000;
const fmt = useSeconds const fmt = useSeconds
? timeFormatOptions.withSeconds ? timeFormatOptions.withSeconds
: timeFormatOptions.edges; : timeFormatOptions.edges;
return new Date(realTime).toLocaleTimeString("en-US", fmt); return new Date(realTime).toLocaleTimeString(
"en-US",
fmt
);
} }
return ""; return "";
}, },
@@ -430,14 +516,16 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
}, },
yAxis: { yAxis: {
type: "value", type: "value",
min: 0.0, min: Math.floor(ymin * 100) / 100,
max: ymax, max: Math.ceil(ymax * 100) / 100,
splitLine: { show: false }, splitLine: { show: false },
}, },
series: series_out.map((s) => ({ series: series_out.map((s) => ({
...s, ...s,
animation: false, // Disable animation for each series animation: false, // Disable animation for each series
animationDuration: 0, animationDuration: 0,
xAxisIndex: 0, // Explicitly set axis index for all series
yAxisIndex: 0,
})), })),
}; };
@@ -451,10 +539,7 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
// Touch event // Touch event
const rect = nativeEvent.target.getBoundingClientRect(); const rect = nativeEvent.target.getBoundingClientRect();
const touch = nativeEvent.touches[0]; const touch = nativeEvent.touches[0];
pixel = [ pixel = [touch.clientX - rect.left, touch.clientY - rect.top];
touch.clientX - rect.left,
touch.clientY - rect.top
];
} else { } else {
// Mouse event // Mouse event
pixel = [nativeEvent.offsetX, nativeEvent.offsetY]; pixel = [nativeEvent.offsetX, nativeEvent.offsetY];
@@ -466,7 +551,9 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
timestamp: mapVirtualToRealTime(dataCoord[0], breaks, virtualTime) / 1000, timestamp:
mapVirtualToRealTime(dataCoord[0], breaks, virtualTime) /
1000,
}), }),
}); });
if (!res.ok) throw new Error(`HTTP error: ${res.status}`); if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
@@ -479,7 +566,7 @@ const EmbedTimeline = React.memo(function EmbedTimeline({
// Chart is ready // Chart is ready
} }
const onEvents = { click: onChartClick }; const onEvents = { click: onChartClick, dataZoom: handleDataZoom };
window.chartRef2 = chartRef; window.chartRef2 = chartRef;
// --- Render --- // --- Render ---
return ( return (

View File

@@ -0,0 +1,61 @@
import pickle
cache_files = ['/mnt/hdd_24tb_1/videos/ftp/leopards1/2025/09/12/embedding_scores@0.0@926895f71538e3683e9af0956af94cf4.pkl', '/mnt/hdd_24tb_1/videos/ftp/leopards1/2025/09/13/embedding_scores@0.0@926895f71538e3683e9af0956af94cf4.pkl', '/srv/ftp_tcc/leopards1/2025/09/14/embedding_scores@0.0@926895f71538e3683e9af0956af94cf4.pkl', '/srv/ftp_tcc/leopards1/2025/09/15/embedding_scores@0.0@926895f71538e3683e9af0956af94cf4.pkl', '/srv/ftp_tcc/leopards1/2025/09/16/embedding_scores@0.0@926895f71538e3683e9af0956af94cf4.pkl', '/srv/ftp_tcc/leopards1/2025/09/17/embedding_scores@0.0@926895f71538e3683e9af0956af94cf4.pkl', '/srv/ftp_tcc/leopards1/2025/09/18/embedding_scores@0.0@926895f71538e3683e9af0956af94cf4.pkl', '/srv/ftp_tcc/leopards1/2025/09/19/embedding_scores@0.0@926895f71538e3683e9af0956af94cf4.pkl']
import time
from datetime import timedelta, datetime
start_time = time.time()
all_c = list()
start_time = 1757892175.042
end_time = 1757894197.548
def check_if_overlap(start_1, end_1, start_2, end_2):
ff = sorted([[start_1, end_1],[start_2, end_2]],key=lambda x: x[0])
return ff[0][1] > ff[1][0]
def get_cache_data(start_time, end_time, cache_files):
targvals = [start_time, end_time]
for f in cache_files:
fold_start_time = datetime(*[int(x) for x in f.split('/')[-4:-1]]).timestamp()
fold_end_time = fold_start_time + 86400.0
has_overlap = check_if_overlap( start_time, end_time, fold_start_time, fold_end_time)
if not has_overlap:
continue
print(f'Loading {f}')
with open(f,'rb') as ff:
all_c.append(pickle.load(ff))
return all_c
st = time.time()
all_cach = get_cache_data(start_time, end_time, cache_files)
vids = list()
for c_c in all_cach:
vids.extend(c_c['videos'])
data_filt = list()
for v in vids:
if check_if_overlap( v['start_time'], v['end_time'], start_time, end_time):
data_filt.append(v)
time_vec = np.hstack([ np.asarray(f['embed_scores']['time'])+f['start_time'] for f in data_filt])
score_vec = np.hstack([f['embed_scores']['score'] for f in data_filt])
s_time, s_ind = np.unique(time_vec, return_index=True)
s_score = score_vec[s_ind]
out_array = np.asarray([s_time, s_score]).T.tolist()

View File

@@ -134,20 +134,22 @@ def calculate_embedding_score_in_folders(c_dirs, threshold, query = None, query_
# kwargs = [{'c_dir':x, 'threshold':threshold, 'query': query} for x in c_dirs] # kwargs = [{'c_dir':x, 'threshold':threshold, 'query': query} for x in c_dirs]
args = [(x, threshold, query, None, logger, redis_key) for x in c_dirs] args = [(x, threshold, query, None, logger, redis_key) for x in c_dirs]
logger.info(f"CALCULATING FOR {args}") # logger.info(f"CALCULATING FOR {args}")
with Pool(processes=8) as pool: with Pool(processes=8) as pool:
out = pool.starmap(calculate_embedding_score_in_folder, args) out = pool.starmap(calculate_embedding_score_in_folder, args)
logger.info(f"DONE CALCULATING FOR {args}") # logger.info(f"DONE CALCULATING FOR {args}")
for x in out: cache_files = list();
for x, cache_file_loc in out:
try: try:
result_list.extend(x['videos']) result_list.extend(x['videos'])
cache_files.append(cache_file_loc);
except Exception as e: except Exception as e:
print(e, x) print(e, x)
return {'videos':result_list} return {'videos':result_list, 'cache_file_locs': cache_files}
def collapse_scores_to_maxmin_avg(folder_scores): def collapse_scores_to_maxmin_avg(folder_scores):
@@ -215,7 +217,7 @@ def calculate_embedding_score_in_folder(og_dir, threshold, query = None, query_v
logger.info(f"LOADED EMBEDDING SCORE FROM CACHE {cache_file_loc}") logger.info(f"LOADED EMBEDDING SCORE FROM CACHE {cache_file_loc}")
message = {'task':'SCORE_CALC_IN_FOLDER_DONE', 'when': str(c_dir), 'time': dt.datetime.now().timestamp(), 'precomputed': True} message = {'task':'SCORE_CALC_IN_FOLDER_DONE', 'when': str(c_dir), 'time': dt.datetime.now().timestamp(), 'precomputed': True}
r.rpush(redis_key, json.dumps(message)) r.rpush(redis_key, json.dumps(message))
return video_json_info return (video_json_info, cache_file_loc)
else: else:
logger.info(f"CACHE FILE IS OLD, DELETING VEC REP FILE AND RECREATING {cache_file_loc}") logger.info(f"CACHE FILE IS OLD, DELETING VEC REP FILE AND RECREATING {cache_file_loc}")
os.remove( get_vec_rep_file_loc(c_dir)) os.remove( get_vec_rep_file_loc(c_dir))
@@ -288,8 +290,8 @@ def calculate_embedding_score_in_folder(og_dir, threshold, query = None, query_v
with open(cache_file_loc, 'wb') as f: with open(cache_file_loc, 'wb') as f:
logger.info(f"WRITING EMBEDDING SCORE TO CACHE {cache_file_loc}") logger.info(f"WRITING EMBEDDING SCORE TO CACHE {cache_file_loc}")
pickle.dump(to_write, f) pickle.dump(to_write, f)
logger.info(f"SAVED EMBEDDING SCORE TO CACHE {cache_file_loc}")
return to_write return (to_write, cache_file_loc)
def get_matching_file_given_filename(web_name, folder_scores): def get_matching_file_given_filename(web_name, folder_scores):

View File

@@ -82,9 +82,6 @@ async def videos_json(
folder_scores["breaks"] = ES.add_breaks_between_videos(folder_scores) folder_scores["breaks"] = ES.add_breaks_between_videos(folder_scores)
folder_scores['videos'] = ES.collapse_scores_to_maxmin_avg(folder_scores) folder_scores['videos'] = ES.collapse_scores_to_maxmin_avg(folder_scores)
session["folder_scores"] = folder_scores
return folder_scores return folder_scores

6
scripts/start_backend.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
cd /home/thebears/Web/Nuggets/SearchInterface/SearchBackend/
/home/thebears/envs/embedding_search_web_server/bin/fastapi dev --host 0.0.0.0 --port 5003 /home/thebears/Web/Nuggets/SearchInterface/SearchBackend/backend.py

6
scripts/start_frontend.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
cd /home/thebears/Web/Nuggets/SearchInterface/SearchFrontend/search_ui
npm run dev

6
scripts/start_vector.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
cd /home/thebears/Web/Nuggets/SearchInterface/VectorService
/home/thebears/envs/embedding_search_web_server/bin/fastapi dev --host 0.0.0.0 --port 5004 /home/thebears/Web/Nuggets/SearchInterface/VectorService/vector_service.py