Browse Source

first commit again

master
zi 3 years ago
commit
f3c53283f8
  1. 383
      !/index.html
  2. 30
      README.md
  3. 97
      album.html
  4. BIN
      icon.png
  5. 72
      index.html
  6. 7
      js/hammer.min.js
  7. 2
      js/jquery-3.3.1.min.js
  8. 104
      pix.py
  9. 30
      write_captionsJSON.py

383
!/index.html

@ -0,0 +1,383 @@
<!doctype html>
<html>
<head>
<link href="../icon.png" rel="shortcut icon"/>
<meta charset="utf-8"/>
<style>
a {
color: rgb(224,220,220);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 18px;
letter-spacing: 2px;
line-height: 12px;
text-transform: uppercase;
}
div#bottom {
background-color: rgba(198, 192, 192, 0.6);
bottom: 0;
color: black;
height: auto;
left: 0;
line-height: 18px;
padding: 8px 32px;
position: absolute;
right: 0;
text-align: center;
text-transform: none; }
div#main {
/* background-color: rgb(112, 107, 107); */
background-color: rgb(0, 0, 0);
background-position: 0 0, 16px 16px;
background-size: 32px 32px;
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: calc(60px - 1vw);
}
div#top_center {
background-color: rgb(104, 100, 100);
color: rgb(224,220,220);
height: calc(60px - 1vw);
left: 25%;
font-size: calc(36px - 1vw);
line-height: 32px;
padding: 8px 32px;
position: absolute;
text-align: center;
top: 0;
right: 25%;
}
div#top_left {
background-color: rgb(104, 100, 100);
color: rgb(224,220,220);
height: calc(60px - 1vw);
left: 0;
line-height: 32px;
padding: 8px 32px;
position: absolute;
text-align: left;
top: 0;
width: 25%;
}
div#top_right {
background-color: rgb(104, 100, 100);
color: rgb(224,220,220);
height: calc(60px - 1vw);
line-height: 32px;
padding: 8px 32px;
position: absolute;
top: 0;
right: 0;
text-align: right;
width: 25%;
}
span {
cursor: pointer;
}
span:hover {
text-decoration: underline;
}
span:active {
color: rgb(178, 56, 50);
}
span.disabled {
cursor: default;
text-decoration: underline;
}
</style>
</head>
<body>
<div id="top_left">
<span id="back">back</span>
</div>
<div id="top_center">
<span id="previous">previous</span> | <span id="play">play</span> | <span id="next">next</span>
</div>
<div id="top_right">
<span id="small">1080p</span> | <span id="large">original</span>
</div>
<div id="main"></div>
<div id="bottom">loading...</div>
</body>
<script type="text/javascript" src="../js/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../js/hammer.min.js"></script>
<script>
var path
var current, previous, next
var img
var imageWidth, imageHeight, imageRatio
var timeout
var back = document.getElementById('back')
var previous = document.getElementById('previous')
var playPause = document.getElementById('play')
var next = document.getElementById('next')
var small = document.getElementById('small')
var large = document.getElementById('large')
var main = document.getElementById('main')
var bottom = document.getElementById('bottom')
var elements = {
27: back,
32: playPause,
37: previous,
39: next,
187: large,
189: small
}
var captions
$.getJSON("../js/captions.json", function(data) {
captions = data;
})
var hammertime = new Hammer(main)
hammertime.get('swipe').set({velocity: 3.0})
function hammerIt(elm) {
hammertime = new Hammer(elm, {});
hammertime.get('pinch').set({
enable: true
});
var posX = 0,
posY = 0,
scale = 1,
last_scale = 1,
last_posX = 0,
last_posY = 0,
max_pos_x = 0,
max_pos_y = 0,
transform = "",
el = elm;
hammertime.on('pan pinch panend pinchend', function(ev) {
//pan
if (scale != 1) {
posX = last_posX + ev.deltaX;
posY = last_posY + ev.deltaY;
max_pos_x = Math.ceil((scale - 1) * el.clientWidth / 2);
max_pos_y = Math.ceil((scale - 1) * el.clientHeight / 2);
if (posX > max_pos_x) {
posX = max_pos_x;
}
if (posX < -max_pos_x) {
posX = -max_pos_x;
}
if (posY > max_pos_y) {
posY = max_pos_y;
}
if (posY < -max_pos_y) {
posY = -max_pos_y;
}
}
//pinch
if (ev.type == "pinch") {
scale = Math.max(.999, Math.min(last_scale * (ev.scale), 4));
}
if(ev.type == "pinchend"){last_scale = scale;}
//panend
if(ev.type == "panend"){
last_posX = posX < max_pos_x ? posX : max_pos_x;
last_posY = posY < max_pos_y ? posY : max_pos_y;
}
if (scale != 1) {
transform =
"translate3d(" + posX + "px," + posY + "px, 0) " +
"scale3d(" + scale + ", " + scale + ", 1)";
}
if (transform) {
el.style.webkitTransform = transform;
}
});
}
function get(url, callback) {
var request = new XMLHttpRequest()
request.open('GET', url, true)
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (request.status == 200) {
callback(request.responseText, null)
} else {
callback(null, {
code: request.status,
text: request.statusText
})
}
}
};
request.send()
}
function loadImage() {
if (!['small', 'large'].includes(localStorage['pix.size'])) {
localStorage['pix.size'] = 'small'
}
small.className = localStorage['pix.size'] == 'small'
? 'disabled' : ''
large.className = localStorage['pix.size'] == 'large'
? 'disabled' : ''
bottom.style.display = 'block'
img = document.createElement('img')
img.style.position = 'absolute'
img.onload = function() {
imageWidth = img.width
imageHeight = img.height
imageRatio = imageWidth / imageHeight
onResize()
document.title = path.split('/').pop().toUpperCase()
// uncomment below for no captions
// bottom.style.display = 'none'
main.innerHTML = ''
main.appendChild(img)
//var img_alt = captions[current.split('/').pop()]
var img_alt = captions[current.split('/').slice(-2).join('/')]
if (img_alt != "") {
img.setAttribute('alt', img_alt)
bottom.innerHTML = img_alt
} else {
bottom.style.display = 'none'
}
}
img.setAttribute('src', '../' + (
localStorage['pix.size'] == 'small'
? current.replace('/', '/1080/') : current
))
hammerIt(img)
}
function mod(a, b) {
return (a % b + b) % b
}
function onHashchange() {
bottom.innerHTML = 'loading...'
current = null
previous = null
next = null
var hash = document.location.hash.slice(1)
if (!hash) return
var parts = hash.split('/')
var name = parts.pop().replace(/%20/g, ' ')
path = parts.join('/')
get('../' + path + '/index.html', function(html, error) {
if (error) return
var matches = html.match(/src="(.*?)"/g).map(function(match) {
return match.slice(5, -1).replace(/^256\//g, '')
})
var index = matches.indexOf(name)
if (index == -1) return
current = path + '/' + name
previous = path + '/' + matches[mod(index - 1, matches.length)]
next = path + '/' + matches[mod(index + 1, matches.length)]
loadImage()
})
}
function onKeydown(e) {
elements[e.keyCode] && elements[e.keyCode].onclick()
}
function onResize() {
var screenWidth = window.innerWidth
var screenHeight = window.innerHeight - 32
var screenRatio = screenWidth / screenHeight
var isSmaller = imageWidth < screenWidth
&& imageHeight < screenHeight
var isWider = imageRatio > screenRatio
img.style.width = (
isSmaller ? imageWidth
: isWider ? screenWidth
: screenHeight * imageRatio
) + 'px'
img.style.height = (
isSmaller ? imageHeight
: isWider ? screenWidth / imageRatio
: screenHeight
) + 'px'
img.style.left = (
isSmaller ? (screenWidth - imageWidth) / 2
: isWider ? 0
: (screenWidth - screenHeight * imageRatio) / 2
) + 'px'
img.style.top = (
isSmaller ? (screenHeight - imageHeight) / 2
: isWider ? (screenHeight - screenWidth / imageRatio) / 2
: 0
) + 'px'
}
function pause() {
timeout && clearTimeout(timeout)
timeout = null
}
function play() {
pause()
timeout = setTimeout(playing, 5000)
function playing() {
document.location.hash = next.replace(/ /g, '%20')
timeout = setTimeout(playing, 5000)
}
}
back.onclick = function() {
document.location = '../' + path
}
previous.onclick = function() {
document.location.hash = previous.replace(/ /g, '%20')
timeout && play()
}
playPause.onclick = function() {
if (playPause.innerHTML == 'play') {
playPause.innerHTML = 'pause'
play()
} else {
playPause.innerHTML = 'play'
pause()
}
}
next.onclick = function() {
document.location.hash = next.replace(/ /g, '%20')
timeout && play()
}
small.onclick = function() {
localStorage['pix.size'] = 'small'
loadImage()
}
large.onclick = function() {
localStorage['pix.size'] = 'large'
loadImage()
}
hammertime.on("swiperight", function(ev) {
document.location.hash = previous.replace(/ /g, '%20')
timeout && play()
})
hammertime.on("swipeleft", function(ev) {
document.location.hash = next.replace(/ /g, '%20')
timeout && play()
})
hammertime.on("tap", function(ev) {
var screenWidth = window.innerWidth
var clickX = ev.center.x
console.log(screenWidth)
if (clickX > screenWidth/2) {
document.location.hash = next.replace(/ /g, '%20')
timeout && play()
} else {
document.location.hash = previous.replace(/ /g, '%20')
timeout && play()
}
})
window.onhashchange = onHashchange
window.onkeydown = onKeydown
window.onresize = onResize
onHashchange()
</script>
</html>

30
README.md

@ -0,0 +1,30 @@
Some changes to a minimal photo gallery (pix)
* works with jpg (ignores png/other)
+ tap for previous/next
+ photos can have user defined captions
+ album titles
_____
Create folders ("albums") here, copy images into these folders.
To generate images (1080, 256), create HTML, run
python3 pix.py
pix.py to be run again if edit album.html and index.html, change the CSS, enable/disable captions, etc. or if photos are added/delete/renamed.
____
Captions -> to create json template using album and file names, run
python3 write_captionsJSON.py
-> generates captions.json in js folder {album/image.ext: "enter-caption-here", ...}
-> open captions.json in txt editor to edit / enter captions
* write_CaptionsJSON.py can be run again in case files are added/deleted
____
The image viewer supports the following keyboard shortcuts:
Escape Back
Left Previous
Space Play/Pause
Right Next
Minus 1080p
Plus Original

97
album.html

@ -0,0 +1,97 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<link href="../icon.png" rel="shortcut icon"/>
<meta charset="utf-8"/>
<style>
a {
color: rgb(128, 128, 128);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
body {
background-color: rgb(192, 192, 192);
color: white;
font-family: Arial, Helvetica, sans-serif;
font-size: 18px;
letter-spacing: 1.5px;
line-height: 18px;
text-transform: capitalize;
}
div#main {
background-color: rgb(162, 155, 152);
background-position: 0 0, 16px 16px;
background-size: 32px 32px;
bottom: 0;
left: 0;
overflow-y: auto;
padding: 16px;
position: absolute;
right: 0;
top: 96px;
}
div#main > div {
display: inline-block;
height: 128px;
margin: 4px;
overflow: hidden;
text-align: center;
width: 128px;
}
div#main > div:after {
content: "";
display: inline-block;
vertical-align: middle;
height: 100%;
}
div#top {
background-color: rgb(104, 100, 100);
color: #cc4039;
height: 32px;
left: 0;
line-height: 32px;
padding: 8px 16px;
position: absolute;
right: 0;
top: 0;
}
div#top > a {
color: rgb(224,220,220);
}
div#top_title {
background-color: rgba(153, 147, 144, 1);
color: black;
height: 32px;
left: 0;
line-height: 32px;
padding: 8px 32px;
position: absolute;
right: 0;
top: 48px;
}
img.landscape {
height: 128px;
margin-left: -32px;
}
img.portrait {
width: 128px;
}
</style>
</head>
<body>
<div id="top">
<a href="../"><< all images</a>
</div>
<div id="top_title">
{title}
</div>
<div id="main">
<!--# You can edit this file, but only above this line. #-->
<!--# You can edit this file, but only below this line. #-->
</div>
</body>
</html>

BIN
icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

72
index.html

@ -0,0 +1,72 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Albums</title>
<link href="pix.png" rel="shortcut icon"/>
<meta charset="utf-8">
<style>
a {
/* color: rgb(128, 128, 128); */
color: rgb(196, 65, 50);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
body {
background-color: rgb(215, 214, 214);
color: black;
font-family: Arial, Helvetica, sans-serif;
font-size: 18px;
letter-spacing: 2px;
line-height: 18px;
/* text-transform: uppercase; */
}
div#main {
background-color: rgb(215, 214, 214);
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 48px;
background-position: 0 0, 16px 16px;
background-size: 32px 32px;
}
div#top {
background-color: rgb(210, 207, 207);
color: #b23832;
height: 32px;
left: 0;
line-height: 32px;
padding: 8px 32px;
position: absolute;
right: 0;
top: 0;
}
div#top > a {
color: #b23832;
}
li {
color: rgb(192, 192, 192);
margin-bottom: 12px;
}
ul {
list-style-type: none;
margin: 32px;
padding: 0;
text-align: left;
}
</style>
</head>
<body>
<div id="top">
<a href="../../"><< back</a>
</div>
<div id="main">
<!--# You can edit this file, but only above this line. #-->
<!--# You can edit this file, but only below this line. #-->
</div>
</body>
</html>

7
js/hammer.min.js

File diff suppressed because one or more lines are too long

2
js/jquery-3.3.1.min.js

File diff suppressed because one or more lines are too long

104
pix.py

@ -0,0 +1,104 @@
import codecs
import os
import re
from PIL import Image, ExifTags
'''
# You can edit the following line with login details
#REMOTE_PATH = 'user@server:path-to-galleryAlbums/'
if not REMOTE_PATH.endswith('/'):
REMOTE_PATH += '/'
'''
# Size of thumbnails
SIZE_THUMBNAIL = 256
# Size of low-resolution images
SIZE_SMALL = 1080
def create_directory(path):
dirname = os.path.dirname(path)
if not os.path.exists(dirname):
os.mkdir(dirname)
def write_html(source, target, html, title=''):
with codecs.open(source, 'r', encoding='utf-8') as f:
html_original = f.read()
html_original = re.sub('\{title\}', title.title(), html_original)
indent = html_original.split('<!--#')[0].split('\n')[-1]
html = re.sub('\n', '\n{}'.format(indent), '\n{}\n'.format(html))
with codecs.open(target, 'w', encoding='utf-8') as f:
f.write(re.sub(
'(?<=#-->).*?(?=<!--#)', html, html_original, flags=re.DOTALL
))
html_index = '<ul>'
# For each album
for path in [f for f in sorted(os.listdir('./')) if os.path.isdir(f) and f != '!' and f.lower() != 'videos' and f.lower() != 'js']:
html_index += ('\n <li><a href="{0}">{0}</a></li>'.format(path))
html_album = ''
# For each image
for name in [f for f in sorted(os.listdir(path)) if f.lower().endswith('.jpg')]:
path_large = os.path.join(path, name)
path_small = os.path.join(path, str(SIZE_SMALL), name)
path_thumbnail = os.path.join(path, str(SIZE_THUMBNAIL), name)
print(path_large)
image = Image.open(path_large)
# Fix orientation
exif = {
ExifTags.TAGS[k]: v
for k, v in (image._getexif() or {}).items()
if k in ExifTags.TAGS
}
orientation = exif.get('Orientation', 1)
if orientation == 6:
image = image.rotate(-90, expand=True)
elif orientation == 8:
image = image.rotate(90, expand=True)
if orientation != 1:
image.save(path_large)
ratio = image.size[0] / image.size[1]
# Create low-resolution image
if not os.path.exists(path_small):
create_directory(path_small)
image.resize(
(int(round(SIZE_SMALL * ratio)), SIZE_SMALL)
if ratio > 1 else
(SIZE_SMALL, int(round(SIZE_SMALL / ratio))),
Image.ANTIALIAS
).save(path_small)
# Create thumbnail
if not os.path.exists(path_thumbnail):
create_directory(path_thumbnail)
image.resize(
(SIZE_THUMBNAIL, int(round(SIZE_THUMBNAIL / ratio)))
if ratio > 1 else
(int(round(SIZE_THUMBNAIL * ratio)), SIZE_THUMBNAIL),
Image.ANTIALIAS
).save(path_thumbnail)
# Create HTML
html_album += (
'{0}<div><a href="../!/#{1}/{2}">'
'<img class="{3}" src="{4}/{2}"/></a></div>'
).format(
'' if not html_album else '\n', path, name,
'landscape' if ratio > 1 else 'portrait', SIZE_THUMBNAIL
)
# Cleanup
for p in [
os.path.join(path, str(SIZE_SMALL)),
os.path.join(path, str(SIZE_THUMBNAIL))
]:
for n in [f for f in os.listdir(p) if f.lower().endswith('.jpg')]:
if not os.path.exists(os.path.join(path, n)):
os.remove(os.path.join(p, n))
# Write album HTML
write_html(
'album.html', os.path.join(path, 'index.html'), html_album, path
)
html_index += '\n</ul>'
# Write index HTML
write_html('index.html', 'index.html', html_index)
# Alternatively, you can comment this out and use FTP / run rsync separately
'''
print("Syncing albums...")
os.system('rsync -av --ignore-existing ./ "{}"'.format(REMOTE_PATH))
'''

30
write_captionsJSON.py

@ -0,0 +1,30 @@
#!/usr/bin/python3
import os
import json
import subprocess
from collections import OrderedDict
#usage: python3 write_captionsJSON.py
# do not overwrite existing captions
if not os.path.exists("js/captions.json"):
captions = {}
else:
captions = json.load(open("js/captions.json"))
subprocess.call(['cp', 'js/captions.json', 'js/captions.json.back'])
# CleanUp needed for renamed files (?how)
# cleanup deleted images
captions = { k : v for k,v in captions.items() if os.path.exists(k)}
# For each album
for path in [f for f in os.listdir('./') if os.path.isdir(f) and f != '!' and not 'videos' in f and f != 'js']:
# For each image
for name in [f for f in sorted(os.listdir(path)) if f.lower().endswith('.jpg')]:
path_name = path + "/" + name
if path_name not in captions.keys():
captions[path_name] = ""
# write 'sorted dict' to file
with open("js/captions.json","w") as j:
json.dump(OrderedDict(sorted(captions.items())), j, indent=2)
Loading…
Cancel
Save