websnap
This commit is contained in:
24
websnap/index.html
Normal file
24
websnap/index.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>websnap</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="wrapper">
|
||||
<!-- the camera -->
|
||||
<video id="video" autoplay playsinline muted></video>
|
||||
<canvas id="overlay"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- overlay for flash -->
|
||||
<div id="flash"></div>
|
||||
|
||||
<canvas id="canvas" style="display:none"></canvas>
|
||||
<!-- main.js ; logic -->
|
||||
<script src="script/main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
49
websnap/run.sh
Executable file
49
websnap/run.sh
Executable file
@@ -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
|
||||
105
websnap/script/main.js
Normal file
105
websnap/script/main.js
Normal file
@@ -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);
|
||||
|
||||
39
websnap/style.css
Normal file
39
websnap/style.css
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user