Added quality of life functions
* Ability to use full path for image path * Ability to configure the wait time between images * Added protection from escaping the image directory
This commit is contained in:
4 files changed
+65
-7
No files matched your search
@@ -16,6 +16,7 @@ A chromecast ambient mode like Diashow
|
||||
1. Install `pyinstaller` using `pip install -U pyinstaller`
|
||||
2. Create binary using `pyinstaller server-pyinstaller.spec`
|
||||
3. The binary is created in the dist folder, to use it create a config an the configured image folder here (or wherever you move the binary to)
|
||||
4. Update the path to the images to a full path
|
||||
|
||||
* if you wish to cleanup you can remove the build (and if you copied the binary somware else the dist folder)
|
||||
* Removing `pyinstaller` is safe you can use `pip uninstall -y altgraph setuptools packaging pyinstaller-hooks-contrib pyinstaller pywin32-ctypes pefile`
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"PORT":8000,
|
||||
"IMAGE_DIR":"images",
|
||||
"IMAGE_TIMEOUT":Minutes the image is shown,
|
||||
"WeatherProvider":"HomeAssistant or MetNO",
|
||||
"METNO_LAT":"MetNO Weather GPS Latitude",
|
||||
"METNO_LON":"MetNO Weather GPS longitude",
|
||||
|
||||
@@ -7,6 +7,8 @@ from PIL import Image
|
||||
from PIL.ExifTags import TAGS
|
||||
import urllib.parse
|
||||
from datetime import datetime
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
|
||||
# --
|
||||
# Configuration
|
||||
@@ -21,6 +23,8 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == "/":
|
||||
self.send_slideshow_page()
|
||||
elif self.path == "/config.js":
|
||||
self.send_clientconf()
|
||||
elif self.path == "/images.json":
|
||||
self.send_image_list()
|
||||
elif self.path == "/weather":
|
||||
@@ -30,8 +34,10 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.get_METNOweather()
|
||||
elif self.path.startswith("/exif"):
|
||||
self.send_exif_data()
|
||||
elif self.path.startswith("/img"):
|
||||
self.send_image()
|
||||
else:
|
||||
super().do_GET()
|
||||
self.send_error(404, "Not found")
|
||||
|
||||
#--
|
||||
# Main Page
|
||||
@@ -49,13 +55,51 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
self.wfile.write(content)
|
||||
|
||||
#--
|
||||
# Image depot
|
||||
#--
|
||||
def send_image(self):
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
params = urllib.parse.parse_qs(parsed.query)
|
||||
filename = params.get("file", [None])[0]
|
||||
|
||||
if not filename:
|
||||
self.send_error(400, "Missing ?file= parameter")
|
||||
return
|
||||
|
||||
base_dir = Path(cfg["IMAGE_DIR"]).resolve()
|
||||
print(base_dir)
|
||||
file_path = (base_dir / filename).resolve()
|
||||
try:
|
||||
file_path.relative_to(base_dir)
|
||||
except ValueError:
|
||||
self.send_error(403, "Forbidden")
|
||||
return
|
||||
|
||||
content_type, _ = mimetypes.guess_type(filename)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", content_type or "application/octet-stream")
|
||||
self.end_headers()
|
||||
with open(cfg['IMAGE_DIR'] + "/" + filename, 'rb') as file:
|
||||
self.wfile.write(file.read())
|
||||
|
||||
#--
|
||||
# Client config
|
||||
#--
|
||||
def send_clientconf(self):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/javascript")
|
||||
self.end_headers()
|
||||
response = f"""const ImgDuration = "{cfg["IMAGE_TIMEOUT"]}";"""
|
||||
self.wfile.write(response.encode("utf-8"))
|
||||
|
||||
#--
|
||||
# Image list
|
||||
#--
|
||||
def send_image_list(self):
|
||||
files = os.listdir(cfg['IMAGE_DIR'])
|
||||
images = [
|
||||
f"/{cfg['IMAGE_DIR']}/{f}"
|
||||
f"{f}"
|
||||
for f in files
|
||||
if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif", ".webp"))
|
||||
]
|
||||
@@ -145,6 +189,7 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
|
||||
#--
|
||||
# Image info
|
||||
#--
|
||||
@@ -157,7 +202,17 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.send_error(400, "Missing ?file= parameter")
|
||||
return
|
||||
|
||||
filepath = filename.lstrip("/")
|
||||
base_dir = Path(cfg["IMAGE_DIR"]).resolve()
|
||||
print(base_dir)
|
||||
file_path = (base_dir / filename).resolve()
|
||||
try:
|
||||
file_path.relative_to(base_dir)
|
||||
except ValueError:
|
||||
self.send_error(403, "Forbidden")
|
||||
return
|
||||
|
||||
|
||||
filepath = cfg['IMAGE_DIR'] +"/"+ filename
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
self.send_error(404, "File not found")
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/config.js"></script>
|
||||
<script>
|
||||
|
||||
// --- Slideshow ---
|
||||
@@ -149,7 +150,7 @@
|
||||
const photoB = document.getElementById('photoB');
|
||||
|
||||
// Start with A visible
|
||||
photoA.style.backgroundImage = 'url(' + images[index] + ')';
|
||||
photoA.style.backgroundImage = 'url(' + "/img?file=" + images[index] + ')';
|
||||
photoA.classList.add('visible');
|
||||
photoInfo(images[index])
|
||||
document.getElementById('photo-Date').textContent = ""
|
||||
@@ -157,17 +158,17 @@
|
||||
setInterval(() => {
|
||||
index = (index + 1) % images.length;
|
||||
if (showingA) {
|
||||
photoB.style.backgroundImage = 'url(' + images[index] + ')';
|
||||
photoB.style.backgroundImage = 'url(' + "/img?file=" + images[index] + ')';
|
||||
photoB.classList.add('visible');
|
||||
photoA.classList.remove('visible');
|
||||
} else {
|
||||
photoA.style.backgroundImage = 'url(' + images[index] + ')';
|
||||
photoA.style.backgroundImage = 'url(' + "/img?file=" + images[index] + ')';
|
||||
photoA.classList.add('visible');
|
||||
photoB.classList.remove('visible');
|
||||
}
|
||||
photoInfo(images[index])
|
||||
showingA = !showingA;
|
||||
}, 5 * 60 * 1000);
|
||||
}, ImgDuration * 60 * 1000);
|
||||
}
|
||||
start();
|
||||
|
||||
|
||||
Reference in new issue
Block a user