Display recorder system highlights

This commit is contained in:
human insan
2026-08-27 23:08:50 +01:00
parent 807447e815
commit baa344c8cb
3 changed files with 40 additions and 1 deletions
+1
View File
@@ -8,6 +8,7 @@ dated video folders into a simple daily view that is easy to search and play.
- Shows recordings on a full 24-hour timeline.
- Uses the time in each filename to place clips at the correct time of day.
- Shows recorded minutes in light blue and empty periods in dark blue.
- Shows recorder-detected system events in dark yellow when `fileindex.dat` is present.
- Lets you choose the recording date.
- Plays browser-friendly MP4 files immediately.
- Converts an AVI clip automatically when no MP4 copy exists.
+15 -1
View File
@@ -70,6 +70,7 @@
.legend .rec::before { background: var(--accent); }
.legend .gap::before { background: #2a2a2a; border: 1px solid #444; }
.legend .highlight::before { background: #ffd43b; }
.legend .system::before { background: #a87900; }
.highlights { margin-top: 18px; background: var(--surface); border-radius: 8px; padding: 14px 16px; }
.highlights h2 { font-size: 14px; font-weight: 500; margin-bottom: 10px; }
.highlight-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
@@ -165,7 +166,7 @@
<canvas id="timeline"></canvas>
</div>
<div class="hours" id="hours"></div>
<div class="legend"><span class="rec">Recorded</span><span class="highlight">Highlight</span><span class="gap">No recording</span></div>
<div class="legend"><span class="rec">Recorded</span><span class="highlight">Highlight</span><span class="system">System highlight</span><span class="gap">No recording</span></div>
</div>
<section class="highlights">
@@ -203,6 +204,7 @@
let zoom = 1;
let focus = 43200;
let highlights = [];
let systemHighlights = [];
let highlightStart = null;
let highlightEnd = null;
let encodingWasRunning = false;
@@ -321,6 +323,11 @@
list.append(row);
});
}
async function loadSystemHighlights() {
const response = await fetch(`/api/system-highlights?date=${dateSelect.value}`);
systemHighlights = await response.json();
drawTimeline();
}
document.querySelector("#mark-start").onclick = () => setMark("start");
document.querySelector("#mark-end").onclick = () => setMark("end");
document.querySelector("#save-highlight").onclick = async () => {
@@ -403,6 +410,12 @@
const r = (item.end - winStart) / winSize * w;
if (r > 0 && l < w) ctx.fillRect(Math.max(0, l), 0, Math.min(w, r) - Math.max(0, l), 56);
});
ctx.fillStyle = "#a87900";
systemHighlights.forEach(item => {
const l = (item.start - winStart) / winSize * w;
const r = (item.end - winStart) / winSize * w;
if (r > 0 && l < w) ctx.fillRect(Math.max(0, l), 0, Math.min(w, r) - Math.max(0, l), 56);
});
// hour lines
ctx.strokeStyle = "rgba(255,255,255,.08)";
for (let h = Math.ceil(winStart / 3600); h < winEnd / 3600; h++) {
@@ -454,6 +467,7 @@
scrubFill.style.width = "0%";
drawTimeline();
loadHighlights();
loadSystemHighlights();
if (!date && d.dates[0]) loadDate(d.dates[0]);
}
+24
View File
@@ -4,8 +4,10 @@ import concurrent.futures
import json
import re
import subprocess
import struct
import threading
import os
from datetime import datetime
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse
@@ -63,6 +65,24 @@ def normalize_date(value):
return date
def scan_system_highlights(root):
try:
data = (root / "fileindex.dat").read_bytes()
except OSError:
return []
highlights = []
for offset in range(0, len(data) - 15, 16):
timestamp, packed, _, _ = struct.unpack_from("<4I", data, offset)
duration = packed & 0xffff
event = (packed >> 16) & 1
if not event or not duration:
continue
moment = datetime.fromtimestamp(timestamp)
start = moment.hour * 3600 + moment.minute * 60 + moment.second
highlights.append({"date": moment.strftime("%Y%m%d"), "start": start, "end": min(86400, start + duration)})
return highlights
class PlayerHandler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
@@ -82,6 +102,9 @@ class PlayerHandler(BaseHTTPRequestHandler):
return self.send_json(self.server.encode_status)
if request.path == "/api/highlights":
return self.send_json(self.server.highlights)
if request.path == "/api/system-highlights":
date = parse_qs(request.query).get("date", [""])[0]
return self.send_json([item for item in self.server.system_highlights if item["date"] == date])
if request.path == "/video":
return self.send_video(request.query)
self.send_error(404)
@@ -276,6 +299,7 @@ def main():
server.output_root = args.root.expanduser().resolve()
server.highlights_path = server.output_root / "highlights.json"
server.highlights = load_highlights(server.highlights_path)
server.system_highlights = scan_system_highlights(server.source_root)
server.encode_status = {"running": False, "total": 0, "completed": 0, "skipped": 0, "failed": 0}
count = sum(map(len, recordings.values()))
print(f"Indexed {count} recordings across {len(recordings)} days")