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.")

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 { log } = console
      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
          console.log(data)
          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
        // log({iwidth,iheight,wwidth,wheight,owidth,oheight})
        t0 = Date.now()
      }
      // log({img,width,height})
      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>