Canvas frame capture
@slashclone/canvas-frame-capture
Check a canvas or WebGL animation frame by frame when the browser cannot take screenshots (a hidden pane, a background tab, a headless run). Captures timed frames from the page itself and saves them as small images you can look at. Writes only to a frames folder you choose.
SkillDevOpsv0.1.0 (latest)
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1"""Save frames sent from a page as images. Standard library only.
- 3Listens on 127.0.0.1 only. Each query parameter of a GET request is one frame: its
- 4name becomes the file name and its value is a data URL (data:image/jpeg;base64,...).
- 5Usage: python3 -I scripts/frame-receiver.py [port] [out_dir] (defaults: 8766 frames)
- 6Stop it with Ctrl-C when the frames are saved.
- 7"""
- 9import base64
- 10import http.server
- 11import os
- 12import re
- 13import sys
- 14import urllib.parse
- 16PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8766
- 17OUT = sys.argv[2] if len(sys.argv) > 2 else "frames"
- 18SAFE = re.compile(r"^[A-Za-z0-9_-]{1,64}$")
- 19os.makedirs(OUT, exist_ok=True)
- 22class Receiver(http.server.BaseHTTPRequestHandler):
- 23 def handle_one_request(self):
- 24 # The default limit is 64 KB per request line; several frames need more.
- 25 self.raw_requestline = self.rfile.readline(8_000_000)
- 26 if not self.raw_requestline or not self.parse_request():
- 27 self.close_connection = True
- 28 return
- 29 if self.command == "GET":
- 30 self.do_GET()
- 31 self.wfile.flush()
- 32 self.close_connection = True
- 34 def do_GET(self):
- 35 query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
- 36 saved = []
- 37 for name, values in query.items():
- 38 value = values[0]
- 39 if not SAFE.match(name) or not value.startswith("data:image/"):
- 40 continue
- 41 with open(os.path.join(OUT, name + ".jpg"), "wb") as f:
- 42 f.write(base64.b64decode(value.split(",", 1)[1]))
- 43 saved.append(name)
- 44 self.send_response(200)
- 45 self.send_header("Content-Type", "text/plain")
- 46 self.end_headers()
- 47 self.wfile.write(("saved " + ", ".join(saved)).encode())
- 48 print("saved", ", ".join(saved) or "nothing", flush=True)
- 50 def log_message(self, *args):
- 51 pass
- 54print(f"Waiting for frames on http://127.0.0.1:{PORT}/ (saving to {OUT}/)", flush=True)
- 55http.server.HTTPServer(("127.0.0.1", PORT), Receiver).serve_forever()
