Photo booth

This commit is contained in:
2026-01-31 18:27:19 +01:00
parent 2d2fc24d71
commit ee97b8faab
8 changed files with 258 additions and 0 deletions

32
photo-booth/README.md Normal file
View File

@@ -0,0 +1,32 @@
# Photo booth web
This is a photo booth that posts the photos shot by the guests to the app.
It shots a photo when the Enter or Space keys are pressed (I used one of these fluffy big USB Enter keys) and uploades it to the app on behalf of an existing user.
## Install
- Create an user that will post the photos adding a row in the user's table. Assign a token, any sufficiently strong string is ok.
- Copy the current folder in the PC that will be used as Photo Booth
- Copy config_example.js to config.js and edit it
- Place your webservices url in BASEURL
- Place the user's token in the config.
- Configure the browser using the following indications:
```
Use Firefox as browser. Go in about:config and set:
browser.download.alwaysOpenPanel false // Prevents to open download list when taking a photo
privacy.webrtc.legacyGlobalIndicator false // Hides camera overlay
When starting firefox in the photo booth totem, press F11 to make it full screen and leave only the Enter or Space key exposed
If the download panel is shown anyway, right click on the download icon and deselect "Show panel when starting a download".
If it appears already deselected, select and deselect it.
```
## Attributions
### Sounds
All creative commons:
- Bike, Bell Ding, Single, 01-01.wav from InspectorJ (https://freesound.org/s/484344/)
- Camera shutter.wav from jdaniel1999 (https://freesound.org/s/376205/)

View File

@@ -0,0 +1,19 @@
// Configuration
const BASEURL = "https://mysite.com";
const TOKEN = ""; // Photo booth user's token
const COUNTDOWN_FROM = 3;
/*
Browser configurations:
Use Firefox as browser. Go in about:config and set:
browser.download.alwaysOpenPanel false // Prevents to open download list when taking a photo
privacy.webrtc.legacyGlobalIndicator false // Hides camera overlay
When starting firefox in the photo booth totem, press F11 to make it full screen and leave only the Enter or Space key exposed
If the download panel is shown anyway, right click on the download icon and deselect "Show panel when starting a download".
If it appears already deselected, select and desenect it.
*/

BIN
photo-booth/ding.mp3 Normal file

Binary file not shown.

19
photo-booth/index.html Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<title>Photo booth</title>
<link rel="stylesheet" href="style.css"/>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript" src="scripts/jquery-3.6.1.min.js"></script>
<script type="text/javascript" src="scripts/script.js"></script>
</head>
<body>
<video id="video" width="1920" height="1080" autoplay></video>
<div id="canvas-container">
<canvas id="canvas" width="3840" height="2160"></canvas>
</div>
<div id="flash"></div>
<div id="countdown">3</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,124 @@
// See https://davidwalsh.name/browser-camera
const shutterSound = new Audio('shutter.mp3');
const countdownSound = new Audio('ding.mp3');
let countingDown = false;
$(document).ready(function() {
// Check if configuration is present
try {
BASEURL
} catch (e) {
alert("config.js not found!");
return;
}
// Shutter button press
$(window).keydown(function(event){
switch(event.keyCode) {
case 13:
case 32:
onShutterButtonPressed();
break
default:
console.log(`Ignored keypress ${event.keyCode}`)
}
});
// Get access to the camera!
var video = $('#video')[0];
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
// Not adding `{ audio: true }` since we only want video now
navigator.mediaDevices.getUserMedia({ video: {
width: 3840,
height: 2160
} }).then(function(stream) {
//video.src = window.URL.createObjectURL(stream);
video.srcObject = stream;
video.play();
});
}
});
function onShutterButtonPressed() {
if (!countingDown)
countdown(COUNTDOWN_FROM, takePhoto);
}
function countdown(from, onCountdownElapsed) {
countingDown = true;
const countdownEl = $('#countdown');
if (from > 0) {
if (from === COUNTDOWN_FROM)
countdownEl.show();
countdownSound.play();
setTimeout(function() {countdown(from - 1, onCountdownElapsed)}, 1000);
} else {
countingDown = false;
countdownEl.hide();
onCountdownElapsed();
}
countdownEl.text(from);
}
function takePhoto() {
const canvas = $('#canvas');
const canvasContainer = $('#canvas-container');
const flash = $('#flash');
const context = canvas[0].getContext('2d');
context.drawImage(video, 0, 0, 3840, 2160);
canvasContainer.show();
flash.show();
$('#video').addClass('blurred');
shutterSound.play();
uploadImage();
// Hide flash
setTimeout(function() {
flash.hide();
}, 200)
// Hide canvas
setTimeout(function() {
canvasContainer.hide();
$('#video').removeClass('blurred');
}, 5000);
}
function uploadImage() {
document.querySelector("#canvas").toBlob(function(blob) {
let file = new File([blob], 'photobooth.jpg', { type: 'image/jpeg' });
let data = new FormData();
data.append('image', file);
let request = new XMLHttpRequest();
request.open('POST', `${BASEURL}/api/gallery_item/upload.php`);
request.setRequestHeader('Authentication', TOKEN);
request.addEventListener('load', function(e) {
console.log(request.response);
});
request.send(data);
saveLocalImage(file);
}, 'image/jpeg', 1);
}
/**
* Saves the image locally for backup
*/
function saveLocalImage(blob) {
const fileName = `photobooth-${Date.now()}`
if (typeof navigator.msSaveBlob == "function")
return navigator.msSaveBlob(blob, fileName);
var saver = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
var blobURL = saver.href = URL.createObjectURL(blob),
body = document.body;
saver.download = fileName;
body.appendChild(saver);
saver.dispatchEvent(new MouseEvent("click"));
body.removeChild(saver);
URL.revokeObjectURL(blobURL);
}

BIN
photo-booth/shutter.mp3 Normal file

Binary file not shown.

62
photo-booth/style.css Normal file
View File

@@ -0,0 +1,62 @@
body {
margin: 0px;
}
#video {
width: 100vw;
height: 100vh;
position: absolute;
cursor: none;
}
.blurred {
filter: blur(8px);
}
#canvas-container {
width: 100vw;
height: 100vh;
position: absolute;
background-color: #000a;
cursor: none;
display: none;
}
#canvas {
width: 50vw;
height: 50vh;
position: relative;
margin-left: 25vw;
margin-top: 25vh;
border: 100px solid white;
left: -100px;
top: -100px;
cursor: none;
}
#flash {
width: 100vw;
height: 100vh;
position: absolute;
display: none;
background-color: white;
cursor: none;
}
#countdown {
width: 600px;
height: 600px;
position: absolute;
left: 50%;
top: 50%;
margin-left: -300px;
margin-top: -300px;
font-size: 450px;
text-align: center;
font-family: sefif;
color: white;
background: #000a;
border-radius: 100px;
cursor: none;
display: none;
}