From 147062b7acec18a6f37173e41898b4474abd36d8 Mon Sep 17 00:00:00 2001 From: aaron Date: Tue, 25 Aug 2026 11:34:32 +0200 Subject: [PATCH] feature(helioviewer): move helioviewer skript to its dedicated git repository --- helioviewer_grab.py | 104 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 helioviewer_grab.py diff --git a/helioviewer_grab.py b/helioviewer_grab.py new file mode 100644 index 0000000..7a2edae --- /dev/null +++ b/helioviewer_grab.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +""" +Holt ein Sonnen-Vollscheibenbild von Helioviewer zu einem Zeitpunkt. + +Beispiel: + ./helioviewer_grab.py 2026-08-12T18:18:00Z hmi_continuum + ./helioviewer_grab.py --list +""" + +import json +import sys +import urllib.parse +import urllib.request + +API = "https://api.helioviewer.org/v2" + + +def call(endpoint, **params): + """GET auf die Helioviewer-API. Gibt (content_type, bytes) zurueck.""" + url = f"{API}/{endpoint}/?" + urllib.parse.urlencode(params) + with urllib.request.urlopen(url, timeout=120) as r: + return r.headers.get_content_type(), r.read() + + +def sources(): + """Flache Liste aller Datenquellen: [(sourceId, layer_path, nickname), ...] + + Der Pfad durch den verschachtelten JSON-Baum IST die Layer-Kette, + die takeScreenshot erwartet (z.B. SDO/HMI/HMI/continuum). + """ + _, raw = call("getDataSources") + out = [] + + def walk(node, path): + if not isinstance(node, dict): + return + if "sourceId" in node: + out.append((node["sourceId"], path, node.get("nickname", ""))) + return + for key, val in node.items(): + walk(val, path + [key]) + + walk(json.loads(raw), []) + return sorted(out) + + +def pick(needle): + """Sucht eine Datenquelle anhand eines Substrings im Nickname/Pfad.""" + hits = [ + s + for s in sources() + if needle.lower().replace("_", " ") + in (" ".join(s[1]) + " " + s[2]).lower().replace("_", " ") + ] + if not hits: + sys.exit(f"keine Datenquelle passt auf {needle!r} (siehe --list)") + return hits[0] + + +def main(): + if "--list" in sys.argv: + for sid, path, nick in sources(): + print(f"{sid:4d} {'/'.join(path):40s} {nick}") + return + + date = sys.argv[1] if len(sys.argv) > 1 else "2026-08-12T18:18:00Z" + what = sys.argv[2] if len(sys.argv) > 2 else "continuum" + + sid, path, nick = pick(what) + print(f"Quelle: [{sid}] {'/'.join(path)} ({nick})") + + # Welches Bild liegt dem Wunschzeitpunkt am naechsten? Wichtig: + # Helioviewer liefert IMMER etwas, auch wenn es Stunden daneben liegt. + _, raw = call("getClosestImage", date=date, sourceId=sid) + meta = json.loads(raw) + print(f"angefragt: {date}\ngeliefert: {meta['date']}") + + layers = "[" + ",".join(path) + ",1,100]" + ctype, png = call( + "takeScreenshot", + date=meta["date"], + imageScale=0.6, # Bogensekunden/Pixel -> ~3400 px Vollscheibe + layers=layers, + x1=-1100, + y1=-1100, + x2=1100, + y2=1100, # Bogensekunden ab Scheibenmitte + display="true", + watermark="false", + ) + if not ctype.startswith("image"): + sys.exit(f"unerwartete Antwort ({ctype}): {png[:200]!r}") + + name = ( + f"{meta['date'].replace(':', '').replace('-', '')}_{nick.replace(' ', '')}.png" + ) + with open(name, "wb") as f: + f.write(png) + print(f"-> {name} ({len(png) / 1e6:.1f} MB)") + + +if __name__ == "__main__": + main()