This commit is contained in:
2025-09-17 16:50:06 -04:00
parent 50376f71a8
commit 0fa6025514
8 changed files with 567 additions and 326 deletions

View File

@@ -1,4 +1,5 @@
{ {
"tabWidth": 2, "tabWidth": 4,
"useTabs": false "useTabs": false,
"experimentalOperatorPosition": "start"
} }

View File

@@ -1753,9 +1753,9 @@
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==" "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="
}, },
"error-ex": { "error-ex": {
"version": "1.3.2", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"requires": { "requires": {
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
@@ -2561,6 +2561,11 @@
"prop-types": "^15.5.4" "prop-types": "^15.5.4"
} }
}, },
"react-table": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz",
"integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA=="
},
"react-transition-group": { "react-transition-group": {
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",

View File

@@ -29,6 +29,7 @@
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-flexbox-grid": "^2.1.2", "react-flexbox-grid": "^2.1.2",
"react-split-pane": "^0.1.92", "react-split-pane": "^0.1.92",
"react-table": "^7.8.0",
"rsuite": "^5.83.3", "rsuite": "^5.83.3",
"timelines-chart": "^2.14.2", "timelines-chart": "^2.14.2",
"uplot": "^1.6.32", "uplot": "^1.6.32",

View File

@@ -9,283 +9,334 @@ import "./App.css";
import StatusesDisplayHUD from "./components/StatusDisplay"; import StatusesDisplayHUD from "./components/StatusDisplay";
function App() { function App() {
const original_data = useRef(null); const original_data = useRef(null);
const chartRef = useRef(null); const chartRef = useRef(null);
const [dataResults, setDataResults] = useState(null); const [dataResults, setDataResults] = useState(null);
const [statusMessages, setStatusMessages] = useState([]); const [statusMessages, setStatusMessages] = useState([]);
const [markerTime, setMarkerTime] = useState(0); const [markerTime, setMarkerTime] = useState(0);
const playerRef = useRef(null); const playerRef = useRef(null);
const playerInstanceRef = useRef(null); const playerInstanceRef = useRef(null);
// State for the values // State for the values
window.chartRef = chartRef; window.chartRef = chartRef;
window.playerRef = playerRef; window.playerRef = playerRef;
window.playerInstanceRef = playerInstanceRef; window.playerInstanceRef = playerInstanceRef;
// Slider states // Slider states
const [sliderMin, setSliderMin] = useState(0.0); const [sliderMin, setSliderMin] = useState(0.0);
const [sliderMax, setSliderMax] = useState(1.0); const [sliderMax, setSliderMax] = useState(1.0);
// Date range states // Date range states
// //
const [startRange, setStartRange] = useState( const [startRange, setStartRange] = useState(
new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000) new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000)
);
const [endRange, setEndRange] = useState(new Date());
// const [endRange, setEndRange] = useState(new Date(new Date().getTime() - 6 * 24 * 60 * 60 * 1000));
const [queryText, setQueryText] = useState("A clouded leopard and a human");
const [sliderValue, setSliderValue] = useState(0);
// State to track last submitted values
const [lastSubmitted, setLastSubmitted] = useState({
startRange,
endRange,
sliderValue,
queryText,
});
// Check if any value has changed
const hasChanged =
startRange !== lastSubmitted.startRange ||
endRange !== lastSubmitted.endRange ||
sliderValue !== lastSubmitted.sliderValue ||
queryText !== lastSubmitted.queryText;
// Function to resubmit fetch
const handleResubmit = () => {
// Start streaming status updates
fetch("api/return_status")
.then((response) => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = ""; // Accumulate partial text
function read() {
reader.read().then(({ done, value }) => {
if (done) {
if (buffer) {
// console.log("Status:", buffer); // Log any remaining text
}
setStatusMessages([]);
// console.log("Status stream finished");
return;
}
// Decode only the new chunk
buffer += decoder.decode(value, { stream: true });
// If your server sends lines, split and log only complete lines:
let lines = buffer.split("\n");
buffer = lines.pop(); // Save incomplete line for next chunk
for (const line of lines) {
if (line.trim()) {
// console.log("Status:", line);
console.log(line)
setStatusMessages((msgs) => [...msgs, JSON.parse(line)]);
}
}
read();
});
}
read();
})
.catch((error) => {
console.error("Error while streaming status:", error);
});
const params = new URLSearchParams();
params.append("startRange", startRange.toISOString());
params.append("endRange", endRange.toISOString());
params.append("threshold", 0.0);
params.append("query", queryText);
setDataResults({ videos: [], breaks: [] });
fetch("api/videos.json?" + params.toString())
.then((res) => res.json())
.then((data) => {
const max_value = Math.max(
...data["videos"].map((vid) => vid["embed_scores"]["score"][1])
);
setSliderMax(max_value);
original_data.current = data;
window.original_data = original_data;
setDataResults(data);
});
setLastSubmitted({ startRange, endRange, sliderValue, queryText });
};
function updateDataAndValue(newValue) {
const floatValue = parseFloat(newValue);
setSliderValue(floatValue);
var newData = JSON.parse(JSON.stringify(original_data.current));
newData["videos"] = newData["videos"].filter(
(vid) => vid["embed_scores"]["score"][1] >= floatValue
); );
setDataResults(newData); const [endRange, setEndRange] = useState(new Date());
} // const [endRange, setEndRange] = useState(new Date(new Date().getTime() - 6 * 24 * 60 * 60 * 1000));
const [queryText, setQueryText] = useState("A clouded leopard and a human");
const [sliderValue, setSliderValue] = useState(0);
function setMarkerValueNonReactive(inputValue) { // State to track last submitted values
let chart = chartRef.current.getEchartsInstance(); const [lastSubmitted, setLastSubmitted] = useState({
let options = chart.getOption(); startRange,
let mappers = options["mappers"]; endRange,
sliderValue,
queryText,
});
let vv = { // Check if any value has changed
xAxis: mappers["real_to_virtual"](new Date(inputValue)), const hasChanged =
lineStyle: { type: "solid", color: "#FF0000", width: 2 }, startRange !== lastSubmitted.startRange ||
label: { endRange !== lastSubmitted.endRange ||
show: false, sliderValue !== lastSubmitted.sliderValue ||
formatter: "Break", queryText !== lastSubmitted.queryText;
position: "bottom",
color: "#888", const streamComputeStatus = () => {
fontSize: 10, fetch("api/return_status")
}, .then((response) => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = ""; // Accumulate partial text
function read() {
reader.read().then(({ done, value }) => {
if (done) {
if (buffer) {
}
setStatusMessages([]);
return;
}
// Decode only the new chunk
buffer += decoder.decode(value, { stream: true });
// If your server sends lines, split and log only complete lines:
let lines = buffer.split("\n");
buffer = lines.pop(); // Save incomplete line for next chunk
for (const line of lines) {
if (line.trim()) {
let c_line = JSON.parse(line);
if (c_line["task"] !== "DONE_QUIT") {
console.log(c_line);
setStatusMessages((msgs) => [
...msgs,
c_line,
]);
}
}
}
read();
});
}
read();
})
.catch((error) => {
console.error("Error while streaming status:", error);
});
};
// Function to resubmit fetch
const handleResubmit = (doTestMode = false) => {
console.log("startRange, endRange:", startRange, endRange);
console.log("test mode:", doTestMode);
let startRangeUse;
let endRangeUse;
if (doTestMode == true) {
startRangeUse = new Date(
new Date().getTime() - 2 * 24 * 60 * 60 * 1000
);
endRangeUse = new Date(
new Date().getTime() - 1 * 24 * 60 * 60 * 1000
);
} else {
startRangeUse = startRange;
endRangeUse = endRange;
}
console.log("Using date range:", startRangeUse, endRangeUse);
setStartRange(startRangeUse);
setEndRange(endRangeUse);
const params = new URLSearchParams();
params.append("startRange", startRangeUse.toISOString());
params.append("endRange", endRangeUse.toISOString());
params.append("threshold", 0.0);
params.append("query", queryText);
setDataResults({ videos: [], breaks: [] });
fetch("api/videos.json?" + params.toString())
.then((res) => res.json())
.then((data) => {
streamComputeStatus();
// Don't setDataResults here, since it's just {"status": ...}
pollForResult(); // Start polling for the real result
});
setLastSubmitted({
startRangeUse,
endRangeUse,
sliderValue,
queryText,
});
}; };
let markLine = { function updateDataAndValue(newValue) {
symbol: ["none", "none"], const floatValue = parseFloat(newValue);
data: [vv], setSliderValue(floatValue);
lineStyle: { type: "dashed", color: "#FF0000", width: 2 }, var newData = JSON.parse(JSON.stringify(original_data.current));
silent: true, newData["videos"] = newData["videos"].filter(
animation: false, (vid) => vid["embed_scores"]["score"][1] >= floatValue
}; );
setDataResults(newData);
// if ("markLine" in options["series"][1]) {
if (false) {
let vv_new = {
xAxis: mappers["real_to_virtual"](new Date(inputValue)),
};
let markLine_new = {
data: [vv_new],
};
chart.setOption(
{
series: [{}, { markLine: { data: [vv_new] } }],
},
false,
["series.markLine"]
);
} else {
chart.setOption(
{
series: [{}, { markLine: markLine }],
},
false,
["series.markLine"]
);
} }
}
// Memoize the timeline click handler function setMarkerValueNonReactive(inputValue) {
const handleTimelineClick = useCallback( let chart = chartRef.current.getEchartsInstance();
(path, timeoffset) => { let options = chart.getOption();
console.log("Timeline clicked:", path, timeoffset); let mappers = options["mappers"];
if (playerRef.current && playerInstanceRef.current) { let vv = {
console.log("Seeking video player to:", path, timeoffset); xAxis: mappers["real_to_virtual"](new Date(inputValue)),
playerInstanceRef.current.src({ lineStyle: { type: "solid", color: "#FF0000", width: 2 },
src: "api/" + path, label: {
type: "video/mp4", show: false,
}); formatter: "Break",
playerInstanceRef.current.on("loadedmetadata", () => { position: "bottom",
playerInstanceRef.current.currentTime(timeoffset); color: "#888",
}); fontSize: 10,
} },
}, };
[] // Empty dependency array since it only uses playerRef
);
useEffect(() => { let markLine = {
const params = new URLSearchParams(window.location.search); // id=123 symbol: ["none", "none"],
data: [vv],
lineStyle: { type: "dashed", color: "#FF0000", width: 2 },
silent: true,
animation: false,
};
if (params.get("test_mode") == "true") { // if ("markLine" in options["series"][1]) {
setStartRange(new Date(new Date().getTime() - 2 * 24 * 60 * 60 * 1000)); if (false) {
setEndRange(new Date(new Date().getTime() - 1 * 24 * 60 * 60 * 1000)); let vv_new = {
xAxis: mappers["real_to_virtual"](new Date(inputValue)),
};
let markLine_new = {
data: [vv_new],
};
chart.setOption(
{
series: [{}, { markLine: { data: [vv_new] } }],
},
false,
["series.markLine"]
);
} else {
chart.setOption(
{
series: [{}, { markLine: markLine }],
},
false,
["series.markLine"]
);
}
} }
handleResubmit();
}, []);
return ( // Memoize the timeline click handler
<div className="app-container"> const handleTimelineClick = useCallback(
<div className="section-box-horiz"> (path, timeoffset) => {
<div className="flex-group"> if (playerRef.current && playerInstanceRef.current) {
<CustomDateRangePicker playerInstanceRef.current.src({
startDate={startRange} src: "api/" + path,
endDate={endRange} type: "video/mp4",
setStartRange={setStartRange} });
setEndRange={setEndRange} playerInstanceRef.current.on("loadedmetadata", () => {
/> playerInstanceRef.current.currentTime(timeoffset);
</div> });
<div className="flex-group"> }
<input },
type="text" [] // Empty dependency array since it only uses playerRef
placeholder="Enter query" );
value={queryText}
onChange={(e) => setQueryText(e.target.value)}
style={{
marginLeft: "16px",
marginRight: "16px",
padding: "8px",
borderRadius: "4px",
border: "1px solid #343a40",
color: "#fff", // Text white
backgroundColor: "#23272f", // Optional: dark background for contrast
}}
/>
</div>
<div className="flex-group">
<label
style={{ marginLeft: "8px", marginRight: "8px", color: "#fff" }}
>
Threshold:
</label>
</div>
<div className="flex-group">
<input
type="range"
min={sliderMin}
max={sliderMax}
step={0.001}
value={sliderValue}
onChange={(e) => updateDataAndValue(e.target.value)}
style={{
width: "120px",
color: "#fff", // Text white
backgroundColor: "#23272f", // Optional: dark background for contrast
}}
/>
</div>
<div className="flex-group">
<span style={{ marginLeft: "8px", color: "#fff" }}>
{sliderValue.toFixed(2)}
</span>
</div>
<div className="flex-group">
<button onClick={handleResubmit}>Resubmit</button>
</div>
</div>
<div>
<StatusesDisplayHUD statusMessages={statusMessages} />
</div>
<div className="timeline-container"> useEffect(() => {
<EmbedTimeline const params = new URLSearchParams(window.location.search);
chartRef={chartRef}
data_in={dataResults} handleResubmit(params.get("test_mode") === "true");
onTimelineClick={handleTimelineClick} }, []);
/>
</div> // useEffect(() => {
<div className="section-box"> // if (startRange && endRange) {
<VideoPlayer // handleResubmit();
videoRef={playerRef} // }
playerInstanceRef={playerInstanceRef} // }, [startRange, endRange]);
setMarkerTimeFunc={setMarkerValueNonReactive}
/> function pollForResult() {
</div> const poll = () => {
</div> fetch("api/videos_result.json")
); .then((res) => res.json())
.then((data) => {
if (data.status === "processing") {
setTimeout(poll, 250); // Try again in 1 second
} else {
const max_value = Math.max(
...data["videos"].map(
(vid) => vid["embed_scores"]["score"][1]
)
);
setSliderMax(max_value);
original_data.current = data;
setDataResults(data);
}
});
};
poll();
}
return (
<div className="app-container">
<div className="section-box-horiz">
<div className="flex-group">
<CustomDateRangePicker
startDate={startRange}
endDate={endRange}
setStartRange={setStartRange}
setEndRange={setEndRange}
/>
</div>
<div className="flex-group">
<input
type="text"
placeholder="Enter query"
value={queryText}
onChange={(e) => setQueryText(e.target.value)}
style={{
marginLeft: "16px",
marginRight: "16px",
padding: "8px",
borderRadius: "4px",
border: "1px solid #343a40",
color: "#fff", // Text white
backgroundColor: "#23272f", // Optional: dark background for contrast
}}
/>
</div>
<div className="flex-group">
<label
style={{
marginLeft: "8px",
marginRight: "8px",
color: "#fff",
}}
>
Threshold:
</label>
</div>
<div className="flex-group">
<input
type="range"
min={sliderMin}
max={sliderMax}
step={0.001}
value={sliderValue}
onChange={(e) => updateDataAndValue(e.target.value)}
style={{
width: "120px",
color: "#fff", // Text white
backgroundColor: "#23272f", // Optional: dark background for contrast
}}
/>
</div>
<div className="flex-group">
<span style={{ marginLeft: "8px", color: "#fff" }}>
{sliderValue.toFixed(2)}
</span>
</div>
<div className="flex-group">
<button onClick={handleResubmit}>Resubmit</button>
</div>
</div>
<div>
<StatusesDisplayHUD statusMessages={statusMessages} />
</div>
<div className="timeline-container">
<EmbedTimeline
chartRef={chartRef}
data_in={dataResults}
onTimelineClick={handleTimelineClick}
/>
</div>
<div className="section-box">
<VideoPlayer
videoRef={playerRef}
playerInstanceRef={playerInstanceRef}
setMarkerTimeFunc={setMarkerValueNonReactive}
/>
</div>
</div>
);
} }
export default App; export default App;

View File

@@ -0,0 +1,90 @@
/* Container */
.table-container {
margin-top: 24px;
padding: 20px;
background: #23272f;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.10);
overflow-x: auto;
}
/* Table */
table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
background: #23272f;
color: #e0e6ed;
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 1rem;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
/* Table Header */
th {
background: linear-gradient(90deg, #2c313c 80%, #23272f 100%);
color: #a9b7c6;
font-weight: 600;
padding: 12px 10px;
border-bottom: 2px solid #3a7afe;
text-align: left;
letter-spacing: 0.03em;
position: sticky;
top: 0;
z-index: 2;
}
/* Table Body */
td {
padding: 10px 10px;
border-bottom: 1px solid #343a40;
background: #23272f;
color: #e0e6ed;
vertical-align: middle;
transition: background 0.2s;
}
/* Row hover */
tr:hover td {
background: #2c313c;
}
/* First column highlight */
td:first-child, th:first-child {
font-weight: 500;
color: #3a7afe;
}
/* Last row no border */
tr:last-child td {
border-bottom: none;
}
/* Table column widths */
table th:last-child,
table td:last-child {
width: 30%;
min-width: 120px;
max-width: 400px;
/* Prevent shrinking below 30% on wide screens */
}
table th:not(:last-child),
table td:not(:last-child) {
width: calc(70% / (var(--col-count, 1)));
/* --col-count should be set to (number of columns - 1) in your table element via JS or React */
}
/* Responsive tweaks */
@media (max-width: 700px) {
.table-container {
padding: 8px;
border-radius: 8px;
}
table {
font-size: 0.95rem;
}
th, td {
padding: 8px 4px;
}
}

View File

@@ -1,67 +1,159 @@
import { useTable } from "react-table";
import { useMemo } from "react";
import "./StatusDisplay.css";
export default function StatusesDisplayHUD({ statusMessages }) { export default function StatusesDisplayHUD({ statusMessages }) {
const msg = {}; const msg = {};
const dataPre = {};
const columns = [
{ Header: "When", accessor: "WHEN" },
{ Header: "Scheduled", accessor: "SCHEDULED" },
{ Header: "Queued", accessor: "QUEUED" },
{ Header: "Processing", accessor: "VECTOR_CALC" },
{ Header: "Calculating", accessor: "SCORE_CALC" },
{ Header: "Status", accessor: "STATUS" },
];
statusMessages.forEach((m) => { statusMessages.forEach((m) => {
let when_key = 'other' let when_key = "other";
if (m['task'] == 'SCHEDULED') if (m["task"] == "SCHEDULED")
m['when'].forEach(( w ) => { msg[w] = 'Scheduled' }) m["when"].forEach((w) => {
msg[w] = "Scheduled";
dataPre[w] = {
SCHEDULED: "✓",
QUEUED: "",
VECTOR_CALC: "",
SCORE_CALC: "",
STATUS: "",
};
});
else { else {
if ('when' in m) if ("when" in m) when_key = m["when"];
when_key = m['when'] let c_task = m["task"];
msg[when_key] = m['task']
let msg_show;
switch (c_task) {
case "SCHEDULED":
msg_show = "Scheduled";
dataPre[when_key]["SCHEDULED"] = "✓";
break;
case "QUEUEING":
msg_show = "In compute queue";
dataPre[when_key]["QUEUED"] = "✓";
break;
case "SCORE_CALC_IN_FOLDER_START":
msg_show = "Calculating Scores";
dataPre[when_key]["VECTOR_CALC"] = "...";
break;
case "VECTOR_CALC_IN_FOLDER_START":
msg_show = "Started processing videos";
dataPre[when_key]["VECTOR_CALC"] = "...";
break;
case "VECTOR_CALC_IN_FOLDER_BUMP":
msg_show =
"Processing videos: " +
m["progress"] +
"/" +
m["how_many"];
dataPre[when_key]["VECTOR_CALC"] =
m["progress"] + "/" + m["how_many"];
break;
case "VECTOR_CALC_IN_FOLDER_DONE":
msg_show = "Finished processing videos";
dataPre[when_key]["VECTOR_CALC"] = "✓";
break;
case "SCORE_CALC_IN_FOLDER_DONE":
msg_show = "Finished calculating scores";
dataPre[when_key]["VECTOR_CALC"] = "✓";
dataPre[when_key]["SCORE_CALC"] = "✓";
break;
default:
msg_show = c_task;
}
msg[when_key] = msg_show;
console.log(when_key);
dataPre[when_key]["STATUS"] = msg_show;
} }
}); });
Object.entries(dataPre).forEach(([k, v]) => {
v["WHEN"] = k;
});
const data = useMemo(() => Object.values(dataPre), [dataPre]);
const columnsMemo = useMemo(() => columns, []);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable({ columns: columnsMemo, data });
return ( return (
<div> <div className="table-container">
{Object.entries(msg).map(([when, messages], idx) => ( <table
<StatusDisplay key={when} when={when} message={messages} /> {...getTableProps()}
))} style={{ "--col-count": columns.length - 1 }}
</div> >
); {rows.length > 0 && (
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>
{column.render("Header")}
</th>
))}
</tr>
))}
</thead>
)}
<tbody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => (
<td {...cell.getCellProps()}>
{cell.render("Cell")}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
);
return (
<div>
{Object.entries(msg).map(([when, messages], idx) => (
<StatusDisplay key={when} when={when} message={messages} />
))}
</div>
);
} }
export function StatusDisplay({ when, message }) {
let msg_show = "";
export function StatusDisplay({when, message }) { msg_show = when + ": " + message;
let msg_show = ''
msg_show = when + ': ' + message
return (
<div
return ( className="status-message"
<div style={{
className="status-message" color: "#fff",
style={{ background: "#23272f",
color: "#fff", padding: "8px",
background: "#23272f", margin: "4px 0",
padding: "8px", borderRadius: "4px",
margin: "4px 0", minHeight: "20px",
borderRadius: "4px", }}
minHeight: "20px", >
}} {msg_show}
> </div>
{msg_show} );
</div>
);
} }
// <div // <div
// className="status-messages" // className="status-messages"
// style={{ // style={{

View File

@@ -39,7 +39,6 @@ def get_matching_file_for_tstamp(target_tstamp, folder_scores):
web_name = 'media/'+os.path.basename(fname) web_name = 'media/'+os.path.basename(fname)
return dict(full_path = fname, path=web_name, timeoffset = offset) return dict(full_path = fname, path=web_name, timeoffset = offset)
def get_vec_rep_file_loc(c_dir): def get_vec_rep_file_loc(c_dir):
vec_rep_file = os.path.join(c_dir, 'vec_rep.npz') vec_rep_file = os.path.join(c_dir, 'vec_rep.npz')
return vec_rep_file return vec_rep_file
@@ -92,7 +91,7 @@ def get_vector_representation(c_dir, force_compute = False, redis_key = 'compute
all_tstamps.append( [x.timestamp() for x in hh['frame_time']]) all_tstamps.append( [x.timestamp() for x in hh['frame_time']])
enu +=1 enu +=1
message = {'task':'VECTOR_CALC_IN_FOLDER_BUMP', 'progress': idx+1, 'how_many': len(sorted_videos), 'time': dt.datetime.now().timestamp()} message = {'task':'VECTOR_CALC_IN_FOLDER_BUMP', 'when': c_dir, 'progress': idx+1, 'how_many': len(sorted_videos), 'time': dt.datetime.now().timestamp()}
r.rpush(redis_key, json.dumps(message)) r.rpush(redis_key, json.dumps(message))
if len(all_cat) == 0: if len(all_cat) == 0:
@@ -104,6 +103,7 @@ def get_vector_representation(c_dir, force_compute = False, redis_key = 'compute
np.savez(vec_rep_file, embeds = all_embeds, idces= all_idces, timestamps = all_times, source_files = all_source) np.savez(vec_rep_file, embeds = all_embeds, idces= all_idces, timestamps = all_times, source_files = all_source)
message = {'task':'VECTOR_CALC_IN_FOLDER_DONE', 'when': str(c_dir), 'time': dt.datetime.now().timestamp()} message = {'task':'VECTOR_CALC_IN_FOLDER_DONE', 'when': str(c_dir), 'time': dt.datetime.now().timestamp()}
r.rpush(redis_key, json.dumps(message))
return dict( embeds = all_embeds, idces= all_idces, timestamps = all_times, source_files = all_source) return dict( embeds = all_embeds, idces= all_idces, timestamps = all_times, source_files = all_source)
@@ -113,7 +113,7 @@ def get_vector_representation(c_dir, force_compute = False, redis_key = 'compute
def get_scores_embedding_c_dir(c_dir, query_vector, redis_key = 'compute_log'): def get_scores_embedding_c_dir(c_dir, query_vector, redis_key = 'compute_log'):
vec_rep = get_vector_representation(c_dir, redis_key=redis_key) vec_rep = get_vector_representation(c_dir, redis_key=redis_key)
query_scores = (query_vector @ vec_rep['embeds'].T).squeeze() query_scores = (query_vector @ vec_rep['embeds'].T).squeeze()
return query_scores return vec_rep, query_scores
@functools.lru_cache @functools.lru_cache
def get_query_vector(query): def get_query_vector(query):
@@ -226,8 +226,8 @@ def calculate_embedding_score_in_folder(og_dir, threshold, query = None, query_v
pass pass
vec_rep = get_vector_representation(c_dir, redis_key = redis_key) # vec_rep = get_vector_representation(c_dir, redis_key = redis_key)
query_scores = get_scores_embedding_c_dir(c_dir, tuple(query_vector.tolist()[0]), redis_key = redis_key) vec_rep, query_scores = get_scores_embedding_c_dir(c_dir, tuple(query_vector.tolist()[0]), redis_key = redis_key)
video_json_info = list() video_json_info = list()
idces_keep = np.where(query_scores > threshold)[0] idces_keep = np.where(query_scores > threshold)[0]

View File

@@ -1,3 +1,4 @@
from typing import Union, Optional, List from typing import Union, Optional, List
from pydantic import BaseModel from pydantic import BaseModel
from fastapi import FastAPI, Request, Depends from fastapi import FastAPI, Request, Depends
@@ -24,9 +25,9 @@ r = redis.Redis(host='localhost', port=6379, db=15)
class VideosPostRequest(BaseModel): class VideosPostRequest(BaseModel):
query: str = "A cat and a human", query: str = "A cat and a human"
threshold: float = 0.10, threshold: float = 0.10
c_dirs: Optional[List[str]] = None, c_dirs: Optional[List[str]] = None
task_id: str = 'compute_log' task_id: str = 'compute_log'
@app.post("/videos.json") @app.post("/videos.json")
@@ -61,9 +62,9 @@ async def videos_json(
] ]
print(','.join([str(x) for x in c_dirs])) # print(','.join([str(x) for x in c_dirs]))
message = {'task':'SCHEDULED','when':[str(x) for x in c_dirs], 'time':time.time()} # message = {'task':'SCHEDULED','when':[str(x) for x in c_dirs], 'time':time.time()}
r.rpush(task_id, json.dumps(message)) # r.rpush(task_id, json.dumps(message))?
for x in c_dirs: for x in c_dirs: