From 8d194d5241b3d75007b7587cec62a0b5076462fb Mon Sep 17 00:00:00 2001 From: Omarchy-Big-PC Date: Fri, 28 Aug 2026 16:47:14 +0100 Subject: [PATCH] Highlight export: CSV/JSON download, same-name highlights disambiguated per day, sorted chronologically --- index.html | 5 +++++ server.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/index.html b/index.html index 991a1a3..4ffe373 100644 --- a/index.html +++ b/index.html @@ -200,6 +200,9 @@ --:--:-- + + +
No highlights yet.
@@ -397,6 +400,8 @@ } document.querySelector("#mark-start").onclick = () => setMark("start"); document.querySelector("#mark-end").onclick = () => setMark("end"); + document.querySelector("#export-csv").onclick = () => location.href = "/api/highlights/export?format=csv"; + document.querySelector("#export-json").onclick = () => location.href = "/api/highlights/export?format=json"; document.querySelector("#save-highlight").onclick = async () => { if (highlightStart === null || highlightEnd === null || highlightStart >= highlightEnd) return; await fetch("/api/highlights", {method: "POST", headers: {"Content-Type": "application/json"}, diff --git a/server.py b/server.py index d241c22..cd01d3b 100644 --- a/server.py +++ b/server.py @@ -102,6 +102,8 @@ 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/highlights/export": + return self.export_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]) @@ -149,6 +151,45 @@ class PlayerHandler(BaseHTTPRequestHandler): return self.send_json(highlight) self.send_error(404) + def export_highlights(self): + fmt = parse_qs(urlparse(self.path).query).get("format", ["csv"])[0].lower() + items = sorted(self.server.highlights, key=lambda h: (h["date"], h["start"], h["id"])) + seen = {} + rows = [] + for item in items: + # same label may repeat across days; within one day duplicates get -2, -3... + key = (item["date"], item["label"]) + seen[key] = seen.get(key, 0) + 1 + name = item["label"] if seen[key] == 1 else f"{item['label']} ({seen[key]})" + def clock(sec): + return f"{sec // 3600:02d}:{sec % 3600 // 60:02d}:{sec % 60:02d}" + rows.append({ + "date": f"{item['date'][:4]}-{item['date'][4:6]}-{item['date'][6:]}", + "name": name, + "start": clock(item["start"]), + "end": clock(item["end"]), + "duration_sec": item["end"] - item["start"], + "clip": f"{item['date']}/{item['start'] // 3600:02d}.{item['start'] % 3600 // 60:02d}.{item['start'] % 60:02d}", + }) + if fmt == "json": + data = json.dumps(rows, indent=2).encode() + ctype = "application/json" + else: + import io + buf = io.StringIO() + writer = __import__("csv").writer(buf) + writer.writerow(["date", "name", "start", "end", "duration_sec", "clip"]) + writer.writerows((r["date"], r["name"], r["start"], r["end"], r["duration_sec"], r["clip"]) for r in rows) + data = buf.getvalue().encode() + ctype = "text/csv" + stamp = datetime.now().strftime("%Y%m%d-%H%M%S") + self.send_response(200) + self.send_header("Content-Type", f"{ctype}; charset=utf-8") + self.send_header("Content-Disposition", f'attachment; filename="highlights-{stamp}.{fmt}"') + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + def handle_upload(self): try: length = int(self.headers.get("Content-Length", "0"))