lang/python/ SimpleSlideshow
This assumes your files are .jpg
files in a 1-deep tree, so example filenames are my_folder_1/my_image.jpg
.
Accessing the url http://the_server:4000/next
returns a simple json document of the form { "path": "path_to/image.jpg" }
.
Accessing http://the_server:4000/
returns the html below, and accessing a url ending in .jpg
attempts to serve the file.
The image slides left-right or up-down depending on aspect ratios, as a kind of poor-man's Ken Burns effect. (At some
point I may try to write a proper Ken Burns implementation, and crossfade between images.)
Python server
#!/usr/bin/env python3
# Python 3 server example
from http.server import BaseHTTPRequestHandler, HTTPServer
from subprocess import run, PIPE
import time
import sys
import re
import random
import os
import json
from glob import glob
from datetime import datetime
from threading import Thread
hostName = ""
serverPort = 4000
index_html = open("index.html").read()
files_glob = "*/*.jpg"
print(f"Indexing")
files = glob(files_glob)
def refresh_worker():
global files
files = glob(files_glob)
class MyServer(BaseHTTPRequestHandler):
def homepage(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes(index_html, "utf-8"))
def refresh(self):
Thread(target=refresh_worker)
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("refresh requested", "utf-8"))
def nextimg(self):
src = random.choice(files)
d = { "path": f"/{src}" }
self.send_as_json(d)
def sendimg(self,src):
print(f"img {src}")
if os.path.exists(src):
self.send_response(200)
self.send_header("Content-type", "image/jpeg")
self.end_headers()
with open(src,"rb") as f:
self.wfile.write(f.read())
else:
self.send_response(404)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes(f"File {src} not found","utf8"))
def send_as_json(self,d,response_code=200):
j = json.dumps(d)
self.send_json(j,response_code)
def send_json(self,j,response_code=200):
self.send_response(response_code)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(bytes(j, "utf-8"))
def do_GET(self):
if self.path == "/favicon.ico":
self.send_response(404)
self.end_headers()
self.wfile.write(b"No favicon")
p = self.path.strip("/")
if p == "":
return self.homepage()
if p == "next":
return self.nextimg()
if p == "refresh":
return self.refresh()
if p.endswith(".jpg"):
return self.sendimg(p)
if p == "quit":
self.send_response(400)
self.end_headers()
self.wfile.write(b"Quitting")
self.server.server_close()
else:
self.send_response(400)
self.end_headers()
self.wfile.write(b"Bad request")
if __name__ == "__main__":
print(f"Starting server")
webServer = HTTPServer((hostName, serverPort), MyServer)
print("Server started http://%s:%s" % (hostName, serverPort))
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")
Python Server for Zip Files
Launch this server.py
in a folder containing .zip files of .jpg
images, and
it will serve those without needing to decompress beforehand.
#!/usr/bin/env python3
# Python 3 server example
from http.server import BaseHTTPRequestHandler, HTTPServer
from subprocess import run, PIPE
import time
import sys
import re
import random
import os
import json
from glob import glob
from datetime import datetime
from threading import Thread
import zipfile
hostName = ""
serverPort = 4000
zips_glob = "*.zip"
print(f"Indexing")
zips = []
files = []
def refresh_worker():
global zips
global files
nzips = glob(zips_glob)
nfiles = []
for z in nzips:
with zipfile.ZipFile(z) as f:
for n in f.namelist():
if n.endswith(".jpg"):
nfiles.append((z,n))
zips = nzips
files = nfiles
refresh_worker()
class MyServer(BaseHTTPRequestHandler):
def homepage(self):
index_html = open("index.html").read()
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes(index_html, "utf-8"))
def refresh(self):
Thread(target=refresh_worker)
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("refresh requested", "utf-8"))
def nextimg(self):
src = random.choice(files)
z,n = src
d = { "path": f"/{z}/{n}" }
self.send_as_json(d)
def sendimg(self,src):
print(f"img {src}")
z, n = src.split("/",1)
print(f"{z=} {n=}")
if os.path.exists(z):
with zipfile.ZipFile(z) as f:
print(f"Opened zip")
try:
with f.open(n,"r") as g:
data = g.read()
self.send_response(200)
self.send_header("Content-type", "image/jpeg")
self.end_headers()
self.wfile.write(data)
return
except Exception as e:
print(f"Exception {e}")
self.send_response(404)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes(f"File {src} not found","utf8"))
def send_as_json(self,d,response_code=200):
j = json.dumps(d)
self.send_json(j,response_code)
def send_json(self,j,response_code=200):
self.send_response(response_code)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(bytes(j, "utf-8"))
def do_GET(self):
if self.path == "/favicon.ico":
self.send_response(404)
self.end_headers()
self.wfile.write(b"No favicon")
p = self.path.strip("/")
if p == "":
return self.homepage()
if p == "next":
return self.nextimg()
if p == "refresh":
return self.refresh()
if p.endswith(".jpg"):
return self.sendimg(p)
if p == "quit":
self.send_response(400)
self.end_headers()
self.wfile.write(b"Quitting")
self.server.server_close()
else:
self.send_response(400)
self.end_headers()
self.wfile.write(b"Bad request")
if __name__ == "__main__":
print(f"Starting server")
webServer = HTTPServer((hostName, serverPort), MyServer)
print("Server started http://%s:%s" % (hostName, serverPort))
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")
HTML Javascript client
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Slideshow</title>
<script>
addEventListener("load",_ => {
const q = (x,y=document) => y.querySelector(x)
const qq = (x,y=document) => Array.from(y.querySelectorAll(x))
const img = new Image()
const fps = 60
const secs = 1000
const kbtime = 5 * secs
const changetime = 60 * secs
document.body.append(img)
let iwidth, iheight
let owidth, oheight
let kbdirection, kbrange, kbinc
let t0
const change = _ => {
fetch("/next").then(response => response.json()).then(data => {
const newimg = new Image()
newimg.src = data.path
newimg.onload = _ => {
img.src = newimg.src
iwidth = newimg.width
iheight = newimg.height
compute()
}
newimg.src = data.path
})
}
const compute = _ => {
started = true
const wwidth = window.innerWidth
const wheight = window.innerHeight
const iratio = iwidth / iheight
const wratio = wwidth / wheight
if( wratio >= iratio ) {
// window is wider than image, so fit width
owidth = wwidth
oheight = iheight * wwidth / iwidth
kbdirection = "vertical"
kbrange = oheight - wheight
} else {
oheight = wheight
owidth = iwidth * wheight / iheight
kbdirection = "horizontal"
kbrange = owidth - wwidth
}
img.width = owidth
img.height = oheight
kbinc = kbrange / kbtime // pixels per millisecond
t0 = Date.now()
}
const tick = _ => {
if(!started) return
const t = Date.now()
const dt = t - t0
const dti = dt / kbtime
const theta = dti * 3.141592654
const dx = ((1+Math.cos(theta))/2) * kbrange
if( kbdirection == "vertical" ) {
img.style.left = "0px"
img.style.top = `-${dx}px`
} else {
img.style.top = "0px"
img.style.left = `-${dx}px`
}
}
window.addEventListener("resize",compute)
let started = false
change()
setInterval(tick,(1000/fps)|0)
setInterval(change,changetime)
})
</script>
<style>
body {
margin: 20px;
padding: 0px;
overflow: hidden;
}
img {
margin: 0px;
padding: 0px;
position: absolute;
top: 0px;
left: 0px;
}
</style>
</head>
<body>
</body>
</html>
HTML Client With Improved Ken Burns effect
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Slideshow</title>
<script>
addEventListener("load",_ => {
const q = (x,y=document) => y.querySelector(x)
const qq = (x,y=document) => Array.from(y.querySelectorAll(x))
const img = new Image()
const fps = 60
const secs = 1000
const kbtime = 10 * secs
const changetime = kbtime
const ease_linear = x => x
const ease_cos = x => 0.5*(1 - Math.cos(x*3.141592654))
let ease = ease_cos
document.body.append(img)
let iw, ih, ir
let ow, oh
let ww, wh, wr
let rect1, rect2
let kbdirection, kbrange, kbinc
let t0
let started = false
const lerp = (a,b,t) => a + t * (b - a)
const change = _ => {
fetch("/next").then(response => response.json()).then(data => {
const newimg = new Image()
newimg.src = data.path
newimg.onload = _ => {
img.src = newimg.src
iw = newimg.naturalWidth
ih = newimg.naturalHeight
compute()
}
newimg.src = data.path
})
}
const compute = _ => {
started = true
ww = window.innerWidth
wh = window.innerHeight
wr = ww/wh
ir = iw/ih
// basically we want to maximally fit a rectangle
// with the same aspect ration as the screen
// inside the source image rectangle,
// then apply the scale factor to that
// fitted rectangle
// fw,fh should be width and height of
// a rectangle with same aspect ration as screen
// and fitting within the image
let fr, fw, fh
if( ir > wr ) {
fh = ih
fw = fh * wr
} else {
fw = iw
fh = fw / wr
}
const rf = 0.3
const s1 = 1 - rf * Math.random()
const s2 = 1 - rf * Math.random()
const w1 = (fw * s1)
const w2 = (fw * s2)
const h1 = (fh * s1)
const h2 = (fh * s2)
let xp1 = Math.random() * 0.5
if( Math.random() > 0.5 ) { xp1 = 1 - xp1 }
const xp2 = 1 - xp1
let yp1 = Math.random() * 0.5
if( Math.random() > 0.5 ) { yp1 = 1 - yp1 }
const yp2 = 1 - yp1
const x1 = xp1 * (iw-w1)
const x2 = xp2 * (iw-w2)
const y1 = yp1 * (ih-h1)
const y2 = yp2 * (ih-h2)
rect1 = { s1, x1, y1, w1, h1 }
rect2 = { s2, x2, y2, w2, h2 }
t0 = Date.now()
tick()
}
const clamp = (a,b,x) => x < a ? a : (x > b ? b : x)
const tick = _ => {
if(!started) return
const t1 = Date.now()
const dt = clamp(0,kbtime,(t1 - t0))
const t = ease(dt / kbtime) // t == lerp parameter
const { s1, x1, y1, w1, h1 } = rect1
const { s2, x2, y2, w2, h2 } = rect2
const x = lerp(x1,x2,t)
const y = lerp(y1,y2,t)
const w = lerp(w1,w2,t)
const h = lerp(h1,h2,t)
const mx = ww / w
const my = wh / h
const ox = (-x * mx)
const oy = (-y * my)
const ow = (iw*mx)
const oh = (ih*my)
img.style.left = `${ox}px`
img.style.top = `${oy}px`
img.style.width = `${ow}px`
img.style.height = `${oh}px`
}
window.addEventListener("resize",compute)
change()
setInterval(tick,(1000/fps)|0)
setInterval(change,changetime)
})
</script>
<style>
body {
margin: 20px;
padding: 0px;
overflow: hidden;
}
img {
margin: 0px;
padding: 0px;
position: absolute;
}
</style>
</head>
<body>
</body>
</html>