Updating slideshow and identity editor
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
ebee258bb3
commit
d019af070d
@ -18,4 +18,5 @@ services:
|
|||||||
- ${PWD}/config/local.json:/website/config/local.json
|
- ${PWD}/config/local.json:/website/config/local.json
|
||||||
- /opt/ketrface/models:/root/.deepface
|
- /opt/ketrface/models:/root/.deepface
|
||||||
# - ${PWD}:/website
|
# - ${PWD}:/website
|
||||||
|
- ${PWD}/frontend:/website/frontend
|
||||||
- ${PWD}/server:/website/server
|
- ${PWD}/server:/website/server
|
||||||
|
@ -18,7 +18,8 @@ function createFace(faceId, photoId, selectable) {
|
|||||||
div.classList.add("face");
|
div.classList.add("face");
|
||||||
div.setAttribute("photo-id", photoId);
|
div.setAttribute("photo-id", photoId);
|
||||||
div.setAttribute("face-id", faceId);
|
div.setAttribute("face-id", faceId);
|
||||||
div.style.backgroundImage = "url(face-data/" + (faceId % 100) + "/" + faceId + "-original.png)";
|
const dir = String(faceId % 100).padStart(2, '0')
|
||||||
|
div.style.backgroundImage = `url(faces/${dir}/${faceId}.jpg)`;
|
||||||
div.addEventListener("click", (event) => {
|
div.addEventListener("click", (event) => {
|
||||||
if (event.shiftKey) { /* identities */
|
if (event.shiftKey) { /* identities */
|
||||||
let faceId = parseInt(event.currentTarget.getAttribute("face-id"));
|
let faceId = parseInt(event.currentTarget.getAttribute("face-id"));
|
||||||
|
@ -77,39 +77,73 @@ body {
|
|||||||
<div id="loading"></div>
|
<div id="loading"></div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
var base, placeholder, info, photos = [], photoIndex;
|
|
||||||
|
|
||||||
function shuffle(array) {
|
let base,
|
||||||
var index = array.length, tmp, random;
|
placeholder,
|
||||||
|
info,
|
||||||
|
photos = [],
|
||||||
|
photoIndex = -1;
|
||||||
|
|
||||||
while (index) {
|
let mode, filter;
|
||||||
random = Math.floor(Math.random() * index);
|
|
||||||
index--;
|
|
||||||
|
|
||||||
// And swap it with the current element.
|
const days = [
|
||||||
tmp = array[index];
|
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
|
||||||
array[index] = array[random];
|
"Saturday"
|
||||||
array[random] = tmp;
|
];
|
||||||
}
|
const months = [
|
||||||
|
"January", "February", "March", "April", "May", "June",
|
||||||
return array;
|
"July", "August", "September", "October", "November", "December"
|
||||||
}
|
];
|
||||||
|
|
||||||
var countdown = 15;
|
|
||||||
|
|
||||||
const days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
|
|
||||||
months = [ "January", "February", "March", "April", "May", "June",
|
|
||||||
"July", "August", "September", "October", "November", "December" ];
|
|
||||||
|
|
||||||
let activeFaces = [];
|
let activeFaces = [];
|
||||||
|
|
||||||
function makeFaceBoxes() {
|
let paused = false,
|
||||||
|
tap = 0;
|
||||||
|
let countdown = 15;
|
||||||
|
let scheduled = false;
|
||||||
|
|
||||||
|
|
||||||
|
const onClick = (e) => {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
if (tap && (now - tap < 300)) {
|
||||||
|
toggleFullscreen();
|
||||||
|
tap = 0;
|
||||||
|
} else {
|
||||||
|
tap = new Date().getTime();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const shuffle = (arr) => {
|
||||||
|
var index = arr.length, tmp, random;
|
||||||
|
while (index) {
|
||||||
|
random = Math.floor(Math.random() * index);
|
||||||
|
index--;
|
||||||
|
tmp = arr[index];
|
||||||
|
arr[index] = arr[random];
|
||||||
|
arr[random] = tmp;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const faceClick = (event, face) => {
|
||||||
|
console.log(face);
|
||||||
|
face.relatedFaces.forEach((photo) => {
|
||||||
|
window.open(base + photo.path);
|
||||||
|
});
|
||||||
|
event.preventDefault = true;
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
event.stopPropagation();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeFaceBoxes = () => {
|
||||||
Array.prototype.forEach.call(document.querySelectorAll('.face'), (el) => {
|
Array.prototype.forEach.call(document.querySelectorAll('.face'), (el) => {
|
||||||
el.parentElement.removeChild(el);
|
el.parentElement.removeChild(el);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Not showing face boxes");
|
if (activeFaces.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const el = document.getElementById("photo"),
|
const el = document.getElementById("photo"),
|
||||||
photo = photos[photoIndex];
|
photo = photos[photoIndex];
|
||||||
@ -129,7 +163,6 @@ return;
|
|||||||
offsetTop = 0;
|
offsetTop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
activeFaces.forEach((face) => {
|
activeFaces.forEach((face) => {
|
||||||
const box = document.createElement("div");
|
const box = document.createElement("div");
|
||||||
box.classList.add("face");
|
box.classList.add("face");
|
||||||
@ -138,20 +171,11 @@ return;
|
|||||||
box.style.top = offsetTop + Math.floor(face.top * height) + "%";
|
box.style.top = offsetTop + Math.floor(face.top * height) + "%";
|
||||||
box.style.width = Math.floor((face.right - face.left) * width) + "%";
|
box.style.width = Math.floor((face.right - face.left) * width) + "%";
|
||||||
box.style.height = Math.floor((face.bottom - face.top) * height) + "%";
|
box.style.height = Math.floor((face.bottom - face.top) * height) + "%";
|
||||||
box.addEventListener("click", (event) => {
|
box.addEventListener("click", (e) => { return faceClick(e, face); });
|
||||||
console.log(face);
|
|
||||||
face.relatedPhotos.forEach((photo) => {
|
|
||||||
window.open(base + photo.path);
|
|
||||||
});
|
|
||||||
event.preventDefault = true;
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function loadPhoto(index) {
|
const loadPhoto = (index) => {
|
||||||
const photo = photos[index],
|
const photo = photos[index],
|
||||||
xml = new XMLHttpRequest(),
|
xml = new XMLHttpRequest(),
|
||||||
url = base + photo.path + "thumbs/scaled/" + photo.filename,
|
url = base + photo.path + "thumbs/scaled/" + photo.filename,
|
||||||
@ -169,23 +193,29 @@ function loadPhoto(index) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xml.onload = function(event) {
|
xml.onload = async (event) => {
|
||||||
info.textContent =
|
info.textContent =
|
||||||
days[taken.getDay()] + ", " +
|
days[taken.getDay()] + ", " +
|
||||||
months[taken.getMonth()] + " " +
|
months[taken.getMonth()] + " " +
|
||||||
taken.getDate() + " " +
|
taken.getDate() + " " +
|
||||||
taken.getFullYear();
|
taken.getFullYear();
|
||||||
document.getElementById("photo").style.backgroundImage = "url(" + encodeURI(url).replace(/\(/g, '%28').replace(/\)/g, '%29') + ")";
|
activeFaces = [];
|
||||||
|
makeFaceBoxes();
|
||||||
|
document.getElementById("photo").style.backgroundImage =
|
||||||
|
`url(${encodeURI(url).replace(/\(/g, '%28').replace(/\)/g, '%29')})`;
|
||||||
countdown = 15;
|
countdown = 15;
|
||||||
tick();
|
tick();
|
||||||
window.fetch("api/v1/photos/faces/" + photo.id).then(res => res.json()).then((faces) => {
|
|
||||||
|
try {
|
||||||
|
const res = await window.fetch("api/v1/photos/faces/" + photo.id);
|
||||||
|
const faces = await res.json();
|
||||||
activeFaces = faces;
|
activeFaces = faces;
|
||||||
makeFaceBoxes(photo);
|
makeFaceBoxes(photo);
|
||||||
}).catch(function(error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
info.textContent += "Unable to obtain face information :(";
|
info.textContent += "Unable to obtain face information :(";
|
||||||
});
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
xml.onerror = function(event) {
|
xml.onerror = function(event) {
|
||||||
info.textContent = "Error loading photo. Trying next photo.";
|
info.textContent = "Error loading photo. Trying next photo.";
|
||||||
@ -196,14 +226,77 @@ function loadPhoto(index) {
|
|||||||
xml.send();
|
xml.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextPhoto() {
|
const prevPhoto = async () => {
|
||||||
photoIndex = (photoIndex + 1) % photos.length;
|
if (photoIndex > 0) {
|
||||||
loadPhoto(photoIndex);
|
photoIndex--;
|
||||||
|
loadPhoto(photoIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode !== 'random/') {
|
||||||
|
photoIndex = photos.length;
|
||||||
|
loadPhoto(photoIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await window.fetch(
|
||||||
|
`api/v1/photos/${mode}${filter.replace(/ +/g, "%20")}`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (data && data.items) {
|
||||||
|
info.textContent = photos.length + " photos found. Shuffling.";
|
||||||
|
photos = shuffle(data.items);
|
||||||
|
photoIndex = (photoIndex + 1) % photos.length;
|
||||||
|
loadPhoto(photoIndex);
|
||||||
|
} else if (data) {
|
||||||
|
photos.push(data);
|
||||||
|
photoIndex = photos.length - 1;
|
||||||
|
loadPhoto(photoIndex);
|
||||||
|
} else {
|
||||||
|
info.textContent = "No photos found for " + filter + ".";
|
||||||
|
}
|
||||||
|
} catch(error) {
|
||||||
|
console.error(error);
|
||||||
|
info.textContent = "Unable to fetch " + mode + "=" + filter + " :(";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var scheduled = false;
|
const nextPhoto = async () => {
|
||||||
|
if (photoIndex < photos.length - 1) {
|
||||||
|
photoIndex++;
|
||||||
|
loadPhoto(photoIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function tick() {
|
if (mode !== 'random/') {
|
||||||
|
photoIndex = 0;
|
||||||
|
loadPhoto(photoIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await window.fetch(
|
||||||
|
`api/v1/photos/${mode}${filter.replace(/ +/g, "%20")}`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (data && data.items) {
|
||||||
|
info.textContent = photos.length + " photos found. Shuffling.";
|
||||||
|
photos = shuffle(data.items);
|
||||||
|
photoIndex = 0;
|
||||||
|
loadPhoto(photoIndex);
|
||||||
|
} else if (data) {
|
||||||
|
photos.push(data);
|
||||||
|
photoIndex = photos.length - 1;
|
||||||
|
loadPhoto(photoIndex);
|
||||||
|
} else {
|
||||||
|
info.textContent = "No photos found for " + filter + ".";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
info.textContent = "Unable to fetch " + mode + "=" + filter + " :(";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tick = () => {
|
||||||
if (scheduled) {
|
if (scheduled) {
|
||||||
clearTimeout(scheduled);
|
clearTimeout(scheduled);
|
||||||
}
|
}
|
||||||
@ -219,16 +312,16 @@ function tick() {
|
|||||||
countdown = 15;
|
countdown = 15;
|
||||||
nextPhoto();
|
nextPhoto();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function schedule() {
|
const schedule = () => {
|
||||||
if (scheduled) {
|
if (scheduled) {
|
||||||
clearTimeout(scheduled);
|
clearTimeout(scheduled);
|
||||||
}
|
}
|
||||||
tick();
|
tick();
|
||||||
}
|
};
|
||||||
|
|
||||||
function toggleFullscreen() {
|
const toggleFullscreen = () => {
|
||||||
if (!document.fullscreenElement) {
|
if (!document.fullscreenElement) {
|
||||||
document.documentElement.requestFullscreen();
|
document.documentElement.requestFullscreen();
|
||||||
} else {
|
} else {
|
||||||
@ -236,104 +329,75 @@ function toggleFullscreen() {
|
|||||||
document.exitFullscreen();
|
document.exitFullscreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
var paused = false,
|
const onKeyDown = (event) => {
|
||||||
tap = 0;
|
switch (event.keyCode) {
|
||||||
|
case 32: /* space */
|
||||||
|
paused = !paused;
|
||||||
|
if (!paused) {
|
||||||
|
tick();
|
||||||
|
} else {
|
||||||
|
document.getElementById("loading").textContent = "||";
|
||||||
|
clearTimeout(scheduled);
|
||||||
|
scheduled = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 37: /* left */
|
||||||
|
prevPhoto();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 39: /* right */
|
||||||
|
nextPhoto();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 13: /* enter */
|
||||||
|
toggleFullscreen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
var tmp = document.querySelector("base");
|
var tmp = document.querySelector("base");
|
||||||
if (tmp) {
|
if (tmp) {
|
||||||
base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/"; /* Make sure there is a trailing slash */
|
/* Make sure there is a trailing slash */
|
||||||
} else {
|
base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/";
|
||||||
base = "/";
|
} else {
|
||||||
|
base = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout = 0;
|
||||||
|
window.addEventListener("resize", (event) => {
|
||||||
|
if (timeout) {
|
||||||
|
window.clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
timeout = window.setTimeout(makeFaceBoxes, 250);
|
||||||
|
});
|
||||||
|
|
||||||
var timeout = 0;
|
document.addEventListener("click", onClick);
|
||||||
window.addEventListener("resize", (event) => {
|
document.addEventListener("keydown", onKeyDown);
|
||||||
if (timeout) {
|
|
||||||
window.clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
timeout = window.setTimeout(makeFaceBoxes, 250);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("click", function(event) {
|
info = document.getElementById("info");
|
||||||
toggleFullscreen();
|
|
||||||
var now = new Date().getTime();
|
|
||||||
if (tap && (now - tap < 300)) {
|
|
||||||
toggleFullscreen();
|
|
||||||
tap = 0;
|
|
||||||
} else {
|
|
||||||
tap = new Date().getTime();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", function(event) {
|
/* Trim off everything up to and including the ? (if its there) */
|
||||||
switch (event.keyCode) {
|
var parts = window.location.href.match(/^.*\?(.*)$/);
|
||||||
case 32: /* space */
|
if (parts) {
|
||||||
paused = !paused;
|
parts = parts[1].split("=");
|
||||||
if (!paused) {
|
if (parts.length == 1) {
|
||||||
tick();
|
mode = "holiday/";
|
||||||
} else {
|
filter = parts[0];
|
||||||
document.getElementById("loading").textContent = "||";
|
|
||||||
clearTimeout(scheduled);
|
|
||||||
scheduled = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 37: /* left */
|
|
||||||
if (photoIndex == 0) {
|
|
||||||
photoIndex = photos.length;
|
|
||||||
}
|
|
||||||
photoIndex--;
|
|
||||||
loadPhoto(photoIndex);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 39: /* right */
|
|
||||||
photoIndex = (photoIndex + 1) % photos.length;
|
|
||||||
loadPhoto(photoIndex);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 13: /* enter */
|
|
||||||
toggleFullscreen();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(event.keyCode);
|
|
||||||
});
|
|
||||||
|
|
||||||
info = document.getElementById("info");
|
|
||||||
|
|
||||||
/* Trim off everything up to and including the ? (if its there) */
|
|
||||||
var parts = window.location.href.match(/^.*\?(.*)$/),
|
|
||||||
mode = "holiday",
|
|
||||||
filter = "";
|
|
||||||
if (parts) {
|
|
||||||
parts = parts[1].split("=");
|
|
||||||
if (parts.length == 1) {
|
|
||||||
filter = parts[0];
|
|
||||||
} else {
|
|
||||||
mode = parts[0];
|
|
||||||
filter = parts[1];
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
filter = "memorial day";
|
mode = parts[0].replace(/\/*$/, '/');
|
||||||
|
filter = parts[1];
|
||||||
}
|
}
|
||||||
|
mode = mode
|
||||||
|
} else {
|
||||||
|
mode = "random/"
|
||||||
|
filter = "";
|
||||||
|
}
|
||||||
|
|
||||||
window.fetch("api/v1/photos/" + mode + "/" + filter.replace(/ +/g, "%20"))
|
nextPhoto();
|
||||||
.then(res => res.json()).then(function(data) {
|
|
||||||
if (data.items.length) {
|
|
||||||
info.textContent = photos.length + " photos found. Shuffling.";
|
|
||||||
photos = shuffle(data.items);
|
|
||||||
photoIndex = -1;
|
|
||||||
nextPhoto();
|
|
||||||
} else {
|
|
||||||
info.textContent = "No photos found for " + filter + ".";
|
|
||||||
}
|
|
||||||
}).catch(function(error) {
|
|
||||||
console.error(error);
|
|
||||||
info.textContent = "Unable to fetch " + mode + "=" + filter + " :(";
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -3,12 +3,15 @@ import json
|
|||||||
import os
|
import os
|
||||||
import piexif
|
import piexif
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
from deepface import DeepFace
|
from deepface import DeepFace
|
||||||
from deepface.detectors import FaceDetector
|
from deepface.detectors import FaceDetector
|
||||||
from retinaface import RetinaFace
|
from retinaface import RetinaFace
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import cv2
|
import cv2
|
||||||
|
|
||||||
from ketrface.util import *
|
from ketrface.util import *
|
||||||
from ketrface.db import *
|
from ketrface.db import *
|
||||||
from ketrface.config import *
|
from ketrface.config import *
|
||||||
@ -25,7 +28,8 @@ model_name = 'VGG-Face' # 'ArcFace'
|
|||||||
detector_backend = 'mtcnn' # 'retinaface'
|
detector_backend = 'mtcnn' # 'retinaface'
|
||||||
model = DeepFace.build_model(model_name)
|
model = DeepFace.build_model(model_name)
|
||||||
|
|
||||||
# Derived from https://github.com/serengil/deepface/blob/master/deepface/detectors/MtcnnWrapper.py
|
# Derived from
|
||||||
|
# https://github.com/serengil/deepface/blob/master/deepface/detectors/MtcnnWrapper.py
|
||||||
# Add parameters to MTCNN
|
# Add parameters to MTCNN
|
||||||
from mtcnn import MTCNN
|
from mtcnn import MTCNN
|
||||||
face_detector = MTCNN(min_face_size = 30)
|
face_detector = MTCNN(min_face_size = 30)
|
||||||
@ -63,7 +67,9 @@ def variance_of_laplacian(image):
|
|||||||
# measure, which is simply the variance of the Laplacian
|
# measure, which is simply the variance of the Laplacian
|
||||||
return cv2.Laplacian(image, cv2.CV_64F).var()
|
return cv2.Laplacian(image, cv2.CV_64F).var()
|
||||||
|
|
||||||
def extract_faces(img, threshold=0.95, allow_upscaling = True, focus_threshold = 100):
|
def extract_faces(
|
||||||
|
img, threshold=0.95, allow_upscaling = True, focus_threshold = 100):
|
||||||
|
|
||||||
if detector_backend == 'retinaface':
|
if detector_backend == 'retinaface':
|
||||||
faces = RetinaFace.detect_faces(
|
faces = RetinaFace.detect_faces(
|
||||||
img_path = img,
|
img_path = img,
|
||||||
@ -103,8 +109,6 @@ def extract_faces(img, threshold=0.95, allow_upscaling = True, focus_threshold =
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
to_drop = []
|
|
||||||
|
|
||||||
# Re-implementation of 'extract_faces' with the addition of keeping a
|
# Re-implementation of 'extract_faces' with the addition of keeping a
|
||||||
# copy of the face image for caching on disk
|
# copy of the face image for caching on disk
|
||||||
for k, key in enumerate(faces):
|
for k, key in enumerate(faces):
|
||||||
@ -182,12 +186,15 @@ def extract_faces(img, threshold=0.95, allow_upscaling = True, focus_threshold =
|
|||||||
|
|
||||||
identity['image'] = Image.fromarray(resized)
|
identity['image'] = Image.fromarray(resized)
|
||||||
|
|
||||||
# for key in to_drop:
|
|
||||||
# faces.pop(key)
|
|
||||||
|
|
||||||
return faces
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description = 'Detect faces in images.')
|
||||||
|
parser.add_argument('photos', metavar='PHOTO', type=int, nargs='*',
|
||||||
|
help='PHOTO ID to scan (default: all unscanned photos)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
print(args)
|
||||||
|
|
||||||
base = '/pictures/'
|
base = '/pictures/'
|
||||||
conn = create_connection('../db/photos.db')
|
conn = create_connection('../db/photos.db')
|
||||||
with conn:
|
with conn:
|
||||||
@ -222,11 +229,6 @@ with conn:
|
|||||||
image = face['image']
|
image = face['image']
|
||||||
print(f'Writing face {j+1}/{len(faces)}')
|
print(f'Writing face {j+1}/{len(faces)}')
|
||||||
|
|
||||||
#face['analysis'] = DeepFace.analyze(img_path = img, actions = ['age', 'gender', 'race', 'emotion'], enforce_detection = False)
|
|
||||||
#face['analysis'] = DeepFace.analyze(img, actions = ['emotion'])
|
|
||||||
|
|
||||||
# TODO: Add additional meta-data allowing back referencing to original
|
|
||||||
# photo
|
|
||||||
face['version'] = 1 # version 1 doesn't add much...
|
face['version'] = 1 # version 1 doesn't add much...
|
||||||
|
|
||||||
data = {k: face[k] for k in set(list(face.keys())) - set(['image', 'facial_area', 'landmarks'])}
|
data = {k: face[k] for k in set(list(face.keys())) - set(['image', 'facial_area', 'landmarks'])}
|
||||||
|
@ -45,7 +45,6 @@ def create_face(conn, face):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
return cur.lastrowid
|
return cur.lastrowid
|
||||||
|
|
||||||
|
|
||||||
def create_face_descriptor(conn, face):
|
def create_face_descriptor(conn, face):
|
||||||
"""
|
"""
|
||||||
Create a new face in the faces table
|
Create a new face in the faces table
|
||||||
|
@ -95,7 +95,7 @@ function euclideanDistance(a, b) {
|
|||||||
let A = bufferToFloat32Array(a);
|
let A = bufferToFloat32Array(a);
|
||||||
let B = bufferToFloat32Array(b);
|
let B = bufferToFloat32Array(b);
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for (let i = 0; i < 128; i++) {
|
for (let i = 0; i < A.length; i++) {
|
||||||
let delta = A[i] - B[i];
|
let delta = A[i] - B[i];
|
||||||
sum += delta * delta;
|
sum += delta * delta;
|
||||||
}
|
}
|
||||||
@ -118,14 +118,13 @@ router.get("/:id?", (req, res) => {
|
|||||||
"identities.*," +
|
"identities.*," +
|
||||||
"GROUP_CONCAT(faces.id) AS relatedFaceIds," +
|
"GROUP_CONCAT(faces.id) AS relatedFaceIds," +
|
||||||
"GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds," +
|
"GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds," +
|
||||||
"GROUP_CONCAT(faces.identityDistance) AS relatedIdentityDistances " +
|
"GROUP_CONCAT(facedescriptors.descriptors) AS relatedIdentityDescriptors " +
|
||||||
"FROM identities " +
|
"FROM identities " +
|
||||||
|
"INNER JOIN facedescriptors ON facedescriptors.id=faces.descriptorId " +
|
||||||
"INNER JOIN faces ON identities.id=faces.identityId " +
|
"INNER JOIN faces ON identities.id=faces.identityId " +
|
||||||
filter +
|
filter +
|
||||||
"GROUP BY identities.id", {
|
"GROUP BY identities.id", {
|
||||||
replacements: {
|
replacements: { id },
|
||||||
id: id
|
|
||||||
},
|
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT,
|
type: photoDB.Sequelize.QueryTypes.SELECT,
|
||||||
raw: true
|
raw: true
|
||||||
}).then((identities) => {
|
}).then((identities) => {
|
||||||
@ -139,10 +138,14 @@ router.get("/:id?", (req, res) => {
|
|||||||
delete identity.relatedFaceIds;
|
delete identity.relatedFaceIds;
|
||||||
delete identity.relatedFacePhotoIds;
|
delete identity.relatedFacePhotoIds;
|
||||||
identity.relatedFaces = relatedFaces.map((faceId, index) => {
|
identity.relatedFaces = relatedFaces.map((faceId, index) => {
|
||||||
|
const distance = euclideanDistance(
|
||||||
|
relatedIdentityDistances[index],
|
||||||
|
identity.descriptors
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
faceId: faceId,
|
faceId: faceId,
|
||||||
photoId: relatedFacePhotos[index],
|
photoId: relatedFacePhotos[index],
|
||||||
distance: parseFloat(relatedIdentityDistances[index] !== undefined ? relatedIdentityDistances[index] : -1)
|
distance
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -951,7 +951,7 @@ router.get("/faces/:id", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/random/:id?", (req, res) => {
|
router.get("/random/:id?", async (req, res) => {
|
||||||
let id = parseInt(req.params.id),
|
let id = parseInt(req.params.id),
|
||||||
filter = "";
|
filter = "";
|
||||||
|
|
||||||
@ -959,54 +959,56 @@ router.get("/random/:id?", (req, res) => {
|
|||||||
console.log("GET /random/" + id);
|
console.log("GET /random/" + id);
|
||||||
filter = "AND id=:id";
|
filter = "AND id=:id";
|
||||||
} else {
|
} else {
|
||||||
filter = "AND faces>0";
|
console.log("GET /random/");
|
||||||
|
filter = "";//AND faces>0";
|
||||||
id = undefined;
|
id = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return photoDB.sequelize.query("SELECT id,duplicate FROM photos WHERE deleted=0 " + filter, {
|
/* If the requested ID is a duplicate, we need to find the original
|
||||||
replacements: {
|
* photo ID */
|
||||||
id: id
|
const results = await photoDB.sequelize.query(
|
||||||
},
|
"SELECT id,duplicate FROM photos WHERE deleted=0 " + filter, {
|
||||||
|
replacements: { id },
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT,
|
type: photoDB.Sequelize.QueryTypes.SELECT,
|
||||||
raw: true
|
raw: true
|
||||||
}).then((results) => {
|
|
||||||
if (!results.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (id) {
|
|
||||||
if (results[0].duplicate) {
|
|
||||||
id = results[0].duplicate;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
id = results[Math.floor(Math.random() * results.length)].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return photoDB.sequelize.query(
|
|
||||||
"SELECT photos.*,albums.path AS path FROM photos " +
|
|
||||||
"INNER JOIN albums ON albums.id=photos.albumId " +
|
|
||||||
"WHERE photos.duplicate=0 AND photos.deleted=0 AND photos.scanned NOT NULL AND photos.id=:id", {
|
|
||||||
replacements: {
|
|
||||||
id: id,
|
|
||||||
},
|
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT,
|
|
||||||
raw: true
|
|
||||||
});
|
|
||||||
}).then(function(photos) {
|
|
||||||
if (!photos.length) {
|
|
||||||
return res.status(404).send({ message: id + " not found." });
|
|
||||||
}
|
|
||||||
const photo = photos[0];
|
|
||||||
for (var key in photo) {
|
|
||||||
if (photo[key] instanceof Date) {
|
|
||||||
photo[key] = moment(photo[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getFacesForPhoto(photo.id).then((faces) => {
|
|
||||||
photo.faces = faces;
|
|
||||||
return res.status(200).json(photo);
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
})
|
|
||||||
|
if (!results.length) {
|
||||||
|
return res.status(404).send({ message: id + " not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
if (results[0].duplicate) {
|
||||||
|
id = results[0].duplicate;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = results[Math.floor(Math.random() * results.length)].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const photos = await photoDB.sequelize.query(
|
||||||
|
"SELECT photos.*,albums.path AS path FROM photos " +
|
||||||
|
"INNER JOIN albums ON albums.id=photos.albumId " +
|
||||||
|
"WHERE photos.duplicate=0 AND photos.deleted=0 AND photos.scanned NOT NULL AND photos.id=:id", {
|
||||||
|
replacements: { id },
|
||||||
|
type: photoDB.Sequelize.QueryTypes.SELECT,
|
||||||
|
raw: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (photos.length === 0) {
|
||||||
|
return res.status(404).send({ message: `Error obtaining ${id}.`});
|
||||||
|
}
|
||||||
|
|
||||||
|
const photo = photos[0];
|
||||||
|
for (var key in photo) {
|
||||||
|
if (photo[key] instanceof Date) {
|
||||||
|
photo[key] = moment(photo[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const faces = await getFacesForPhoto(photo.id);
|
||||||
|
photo.faces = faces;
|
||||||
|
return res.status(200).json(photo);
|
||||||
|
});
|
||||||
|
|
||||||
router.get("/mvimg/*", function(req, res/*, next*/) {
|
router.get("/mvimg/*", function(req, res/*, next*/) {
|
||||||
let limit = parseInt(req.query.limit) || 50,
|
let limit = parseInt(req.query.limit) || 50,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user