From 8cdb51802258f8a964f1dd6812729579000aaa90 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sat, 14 Mar 2026 17:58:18 +0100 Subject: [PATCH] websnap --- websnap/index.html | 24 ++++++++++ websnap/run.sh | 49 +++++++++++++++++++ websnap/script/main.js | 105 +++++++++++++++++++++++++++++++++++++++++ websnap/style.css | 39 +++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 websnap/index.html create mode 100755 websnap/run.sh create mode 100644 websnap/script/main.js create mode 100644 websnap/style.css diff --git a/websnap/index.html b/websnap/index.html new file mode 100644 index 0000000..76fad8f --- /dev/null +++ b/websnap/index.html @@ -0,0 +1,24 @@ + + + + +websnap + + + + + +
+ + + +
+ + +
+ + + + + + diff --git a/websnap/run.sh b/websnap/run.sh new file mode 100755 index 0000000..d485c16 --- /dev/null +++ b/websnap/run.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +BG=false +for arg in "$@"; do + [[ "$arg" == "bg" || "$arg" == "--bg" ]] && BG=true +done + +log() { $BG || echo "$@"; } + +# Find the HTML file +HTML_FILE=$(find . -maxdepth 2 -name "*.html" | head -1) + +if [ -z "$HTML_FILE" ]; then + log "No HTML file found." + exit 1 +fi + +# Pick a free port +PORT=8000 +while lsof -i :$PORT &>/dev/null; do + PORT=$((PORT + 1)) +done + +DIR=$(dirname "$HTML_FILE") +FILE=$(basename "$HTML_FILE") + +log "Serving $HTML_FILE on http://localhost:$PORT/$FILE" + +open_browser() { + local url=$1 + if command -v xdg-open &>/dev/null; then + xdg-open "$url" + elif command -v open &>/dev/null; then + open "$url" + elif command -v start &>/dev/null; then + start "$url" + else + log "open: $url" + fi +} + +(sleep 0.5 && open_browser "http://localhost:$PORT/$FILE") & + +if $BG; then + python3 -m http.server "$PORT" --directory "$DIR" &>/dev/null & + disown +else + python3 -m http.server "$PORT" --directory "$DIR" +fi diff --git a/websnap/script/main.js b/websnap/script/main.js new file mode 100644 index 0000000..30367ab --- /dev/null +++ b/websnap/script/main.js @@ -0,0 +1,105 @@ +/* main.js - websnap */ +import { HandLandmarker, FilesetResolver } + from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest"; + +/* set html elements */ +const video = document.getElementById("video"); +const canvas = document.getElementById("canvas"); +const overlay = document.getElementById("overlay"); +const octx = overlay.getContext("2d"); +const ctx = canvas.getContext("2d"); +const flash = document.getElementById("flash"); + +let lastShot = 0; + +/* function to request fullscreen */ +function requestFS() { + const el = document.documentElement; + (el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen) + ?.call(el); +} +requestFS(); + +/* add listeners for fullscreen */ +document.addEventListener("click", requestFS, { once: true }); +document.addEventListener("touchend", requestFS, { once: true }); +document.addEventListener("dblclick", requestFS); + +/* camera setup */ +const stream = await navigator.mediaDevices.getUserMedia({ + /* ideal res: 4096x2160 ; 4k */ + video: { width: { ideal: 4096 }, height: { ideal: 2160 }, facingMode: "user" } +}); +video.srcObject = stream; + +/* import mediapipe for handgestures */ +const vision = await FilesetResolver.forVisionTasks( + "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm" +); +const handLandmarker = await HandLandmarker.createFromOptions(vision, { + baseOptions: { + modelAssetPath: + "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task" + }, + runningMode: "VIDEO", + numHands: 1 +}); + +/* flash for when picture gets taken */ +function triggerFlash() { + flash.classList.add("pop"); + requestAnimationFrame(() => requestAnimationFrame(() => flash.classList.remove("pop"))); +} + +/* capture a picture */ +function takePhoto() { + triggerFlash(); + const w = video.videoWidth, h = video.videoHeight; + canvas.width = w; canvas.height = h; + ctx.drawImage(video, 0, 0, w, h); + canvas.toBlob(blob => { + if (!blob) return; + saveAs(blob, "websnap-" + Date.now() + ".png"); + }, "image/png"); +} + +/* detect shit */ +function detectLoop(timestamp) { + const dw = overlay.offsetWidth, dh = overlay.offsetHeight; + if (overlay.width !== dw || overlay.height !== dh) { + overlay.width = dw; overlay.height = dh; + } + /* draw rect */ + octx.clearRect(0, 0, dw, dh); + + if (video.readyState < 2) { requestAnimationFrame(detectLoop); return; } + + const result = handLandmarker.detectForVideo(video, timestamp); + + if (result.landmarks.length) { + const lm = result.landmarks[0]; + + /* finger indexes, for peace sign */ + const indexUp = lm[8].y < lm[6].y; + const middleUp = lm[12].y < lm[10].y; + const ringDown = lm[16].y > lm[14].y; + const pinkyDown = lm[20].y > lm[18].y; + const fingerGap = Math.abs(lm[8].x - lm[12].x); + const peace = + indexUp && + middleUp && + ringDown && + pinkyDown && + fingerGap > 0.05; + + if (peace && Date.now() - lastShot > 2500) { /* wait a bit */ + takePhoto(); + lastShot = Date.now(); + } + } + + requestAnimationFrame(detectLoop); +} + +requestAnimationFrame(detectLoop); + diff --git a/websnap/style.css b/websnap/style.css new file mode 100644 index 0000000..6e547bf --- /dev/null +++ b/websnap/style.css @@ -0,0 +1,39 @@ +*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } + +html, body { + width: 100%; height: 100%; + background: #000; + overflow: hidden; +} + +#wrapper { + position: fixed; + inset: 0; +} + +video { + width: 100%; height: 100%; + object-fit: cover; + display: block; +} + +#overlay { + position: absolute; + inset: 0; + width: 100%; height: 100%; + pointer-events: none; +} + +#flash { + position: fixed; + inset: 0; + background: white; + opacity: 0; + pointer-events: none; + transition: opacity 0.20s ease-in; +} +#flash.pop { + opacity: 1; + transition: none; +} +