Improved slideshow to support ?holiday URL matching

Added docker config

Signed-off-by: James Ketrenos <james_gitlab@ketrenos.com>
This commit is contained in:
James Ketrenos 2019-11-28 01:54:02 -08:00
parent 55a9c16e7b
commit c2f72df80a
11 changed files with 326 additions and 215 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
*
!entrypoint.sh

1
.env Normal file
View File

@ -0,0 +1 @@
USER_ID=1000:1000

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ node_modules
./elements ./elements
frontend/bower_components frontend/bower_components
pictures pictures
db
*.db

52
Dockerfile Normal file
View File

@ -0,0 +1,52 @@
FROM ubuntu:disco
RUN apt-get update
RUN apt-get install -y wget
# Upgrade Node
RUN wget -qO- https://deb.nodesource.com/setup_10.x | bash -
RUN DEBIAN_FRONTEND=NONINTERACTIVE apt-get install -y \
nodejs \
gcc \
g++ \
make \
cmake
# You can then install the latest npm, polymer-cli, and bower:
RUN npm install --global npm@latest npx
# Speed up face-recognition and dev tools
RUN apt-get install -y libopenblas-dev cmake
# NEF processing uses ufraw-batch
RUN apt-get install -y ufraw-batch
# Create a user with sudo access
RUN DEBIAN_FRONTEND=noninteractive \
&& apt-get install --no-install-recommends -y \
sudo
# NOTE: Requires 'sudo' package to already be installed
RUN groupadd -g 1000 user \
&& useradd --no-log-init \
-s /bin/bash \
-u 1000 \
-m \
-g user \
-G sudo \
-p $(echo "user" | openssl passwd -stdin) user
# Set 'sudo' to NOPASSWD for all container users
RUN sed -i -e 's,%sudo.*,%sudo ALL=(ALL) NOPASSWD:ALL,g' /etc/sudoers
COPY /entrypoint.sh /entrypoint.sh
RUN DEBIAN_FRONTEND=noninteractive \
&& apt-get install --no-install-recommends -y \
git
USER user
WORKDIR /website
CMD [ "/entrypoint.sh" ]

View File

@ -28,47 +28,6 @@ sudo apt install -y ufraw-batch
### Create `photos` user for DB ### Create `photos` user for DB
You will need to know the root password for your mariadb installation. If you Photos is currently using sqlite, which means you don't need to
need to reset the root password, you can perform the following: do anything fancy beyond having run 'npm install' to get sequelize
and sqlite3 installed.
```bash
export PASSWORD=m4g1cP4ssw0rd
sudo service mysql stop
sudo mysqld_safe --skip-grant-tables &
sleep 1 # mysqld_safe can take a bit of time to come online
sudo mysql << EOF
use mysql;
UPDATE user SET PASSWORD=PASSWORD("${PASSWORD}") WHERE USER='root';
UPDATE user SET plugin='mysql_native_password' WHERE USER='root';
FLUSH PRIVILEGES;
QUIT
EOF
sudo service mysql stop
sudo service mysql start
```
Once you know the root password, you can then create a new user and DB
for the photo app via:
### Create the USER
```bash
U='photos'
P='p4$$w0rd'
mysql -u root --password=${PASSWORD} << EOF
CREATE USER '${U}'@'localhost' IDENTIFIED BY '${P}';
EOF
```
### Create the DB 'photos'
```bash
U='photos'
P='p4$$w0rd'
D='photos'
mysql -u root --password=${PASSWORD} << EOF
DROP DATABASE IF EXISTS ${D};
CREATE DATABASE ${D} CHARACTER SET utf8 COLLATE utf8_general_ci;
GRANT ALL PRIVILEGES ON ${D}.* TO '${U}'@'localhost' IDENTIFIED BY '${P}';
EOF
```

View File

@ -1,14 +1,14 @@
{ {
"db": { "db": {
"photos": { "photos": {
"host": "sqlite:photos.db", "host": "sqlite:db/photos.db",
"options": { "options": {
"logging" : false, "logging" : false,
"timezone": "+00:00" "timezone": "+00:00"
} }
}, },
"users": { "users": {
"host": "sqlite:users.db", "host": "sqlite:db/users.db",
"options": { "options": {
"logging" : false, "logging" : false,
"timezone": "+00:00" "timezone": "+00:00"

30
docker-compose.yml Normal file
View File

@ -0,0 +1,30 @@
version: '3.1'
services:
# db:
# image: mariadb
# restart: always
# environment:
# MYSQL_ROOT_PASSWORD: photos
# PHOTOS_DB_USER: photos
# PHOTOS_DB_PASSWD: ph0t0z
# PHOTOS_DB: photos
# volumes:
# - ${PWD}/db:/var/lib/mysql
# - ./init.sql:/data/application/init.sql
photos:
user: ${USER_ID}
build: .
image: photos:latest
container_name: photos
# depends_on:
# - db
restart: always
ports:
- 8123:8123
volumes:
- ${PWD}/photos:/photos
- ${PWD}/db:/db
- ${PWD}:/website

5
entrypoint.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
while true; do
npm start
sleep 3
done

View File

@ -1,6 +1,5 @@
<html> <html>
<script src="src/ketr-photos/fetch.js"></script> <base href="/photos/">
<base href="/">
<body> <body>
<style> <style>
body { body {
@ -15,6 +14,7 @@ body {
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 0;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 50% 50%; background-position: 50% 50%;
background-size: contain; background-size: contain;
@ -27,25 +27,40 @@ body {
display: none; display: none;
} }
#info { #footer {
position: absolute; position: absolute;
bottom: 0px; bottom: 0px;
left: 0px; left: 0px;
right: 0px; right: 0px;
padding: 0.5em 1em; opacity: 0.6;
background-color: #444; background: linear-gradient(45deg, rgba(16, 16, 16, 1), transparent);
color: white; color: white;
font-size: 1.5em; font-size: 1.5em;
line-height: 1.5em; line-height: 1.5em;
font-family: Arial; font-family: Arial;
box-shadow: 0px -0.25em 1em black; box-shadow: 0px -0.25em 1em black;
text-shadow: 0 1px 0 black;
z-index: 100;
display: flex;
flex-direction: row;
justify-content: space-between;
} }
#info {
padding: 0.5em 1em;
}
#loading {
padding: 0.5em 1em;
}
</style> </style>
<img id="placeholder"></img> <img id="placeholder"></img>
<div id="photo"> <div id="photo">
</div> </div>
<div id="info"> <div id="footer">
Loading photoset... <div id="info">Loading photoset...</div>
<div id="loading"></div>
</div> </div>
<script> <script>
var base, placeholder, info, photos = [], photoIndex; var base, placeholder, info, photos = [], photoIndex;
@ -66,9 +81,48 @@ function shuffle(array) {
return array; return array;
} }
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" ];
function loadPhoto(index) { function loadPhoto(index) {
var photo = photos[index]; const photo = photos[index],
placeholder.src = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename; xml = new XMLHttpRequest(),
url = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename,
taken = new Date(photo.taken);
document.getElementById("loading").textContent = "0%";
xml.onprogress = function (event) {
var alpha = 0;
if (event.total) {
alpha = event.loaded / event.total;
document.getElementById("loading").textContent = Math.ceil(100 * alpha) + "%";
} else {
document.getElementById("loading").textContent = "0%";
}
};
xml.onload = function(event) {
info.textContent =
days[taken.getDay()] + ", " +
months[taken.getMonth()] + " " +
taken.getDate() + " " +
taken.getFullYear();
document.getElementById("photo").style.backgroundImage = "url(" + url + ")";
countdown = 15;
tick();
}
xml.onerror = function(event) {
info.textContent = "Error loading photo. Trying next photo.";
nextPhoto();
}
xml.open("GET", url, true);
xml.send();
} }
function nextPhoto() { function nextPhoto() {
@ -77,16 +131,33 @@ function nextPhoto() {
} }
var scheduled = false; var scheduled = false;
function tick() {
if (scheduled) {
clearTimeout(scheduled);
}
document.getElementById("loading").textContent = countdown + "s";
if (countdown > 0) {
/* If there is a timer running, then decrement the counter */
if (scheduled) {
countdown--;
}
scheduled = setTimeout(tick, 1000);
} else {
scheduled = null;
countdown = 15;
nextPhoto();
}
}
function schedule() { function schedule() {
if (scheduled) { if (scheduled) {
clearTimeout(scheduled); clearTimeout(scheduled);
} }
scheduled = setTimeout(function () { tick();
scheduled = undefined;
nextPhoto();
}, 15 * 1000);
} }
var paused = false;
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
var tmp = document.querySelector("base"); var tmp = document.querySelector("base");
if (tmp) { if (tmp) {
@ -96,6 +167,18 @@ document.addEventListener("DOMContentLoaded", function() {
} }
document.addEventListener("keydown", function(event) { document.addEventListener("keydown", function(event) {
if (event.keyCode == 32) { /* space */
paused = !paused;
if (!paused) {
tick();
} else {
document.getElementById("loading").textContent = "||";
clearTimeout(scheduled);
scheduled = null;
}
return;
}
if (event.keyCode == 37) { /* left */ if (event.keyCode == 37) { /* left */
if (photoIndex == 0) { if (photoIndex == 0) {
photoIndex = photos.length; photoIndex = photos.length;
@ -115,52 +198,27 @@ document.addEventListener("DOMContentLoaded", function() {
}); });
info = document.getElementById("info"); info = document.getElementById("info");
placeholder = document.getElementById("placeholder");
placeholder.onload = function() { /* Trim off everything up to and including the ? (if its there) */
var item = photos[photoIndex], var parts = window.location.href.match(/^.*\?(.*)$/);
days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], if (parts) {
months = [ "January", "February", "March", "April", "May", "June", holiday = parts[1];
"July", "August", "September", "October", "November", "December" ], } else {
taken = new Date(item.taken); holiday = "memorial day";
info.textContent = "Photo taken on " +
days[taken.getDay()] + ", " +
months[taken.getMonth()] + " " +
taken.getDate() + " " +
taken.getFullYear();
photo.style.backgroundImage = "url(" + placeholder.src + ")";
schedule();
};
placeholder.onerror = function() {
info.textContent = "Error loading photo. Trying next photo.";
nextPhoto();
} }
window.fetch("api/v1/photos/holiday/" + holiday.replace(/ +/g, "%20"))
window.fetch("api/v1/photos/holiday/memorial%20day", function(error, xhr) { .then(res => res.json()).then(function(data) {
if (error && xhr.status != 404) { if (data.items.length) {
console.log("Unable to fetch holiday: " + error); info.textContent = photos.length + " photos found. Shuffling.";
return; photos = shuffle(data.items);
}
var results;
try {
results = JSON.parse(xhr.responseText);
} catch (___) {
info.textContent = "Unable to parse results"
return;
}
if (results.items.length) {
photos = shuffle(results.items);
photoIndex = -1; photoIndex = -1;
nextPhoto(); nextPhoto();
} else { } else {
info.textContent = "No photos found."; info.textContent = "No photos found for " + holiday + ".";
} }
}).catch(function(error) {
console.error(error);
info.textContent = "Unable to fetch holiday :(";
}); });
}); });
</script> </script>

View File

@ -12,6 +12,9 @@
}, },
"author": "James Ketrenos", "author": "James Ketrenos",
"license": "Apache-2.0", "license": "Apache-2.0",
"unused": {
"face-recognition": "^0.9.4"
},
"dependencies": { "dependencies": {
"bluebird": "^3.5.3", "bluebird": "^3.5.3",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
@ -21,7 +24,6 @@
"exif-reader": "github:paras20xx/exif-reader", "exif-reader": "github:paras20xx/exif-reader",
"express": "^4.16.4", "express": "^4.16.4",
"express-session": "^1.15.6", "express-session": "^1.15.6",
"face-recognition": "^0.9.4",
"handlebars": "^4.0.12", "handlebars": "^4.0.12",
"ldapauth-fork": "^4.0.2", "ldapauth-fork": "^4.0.2",
"ldapjs": "^1.0.2", "ldapjs": "^1.0.2",

View File

@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
sqlite3 photos.db sqlite3 db/photos.db