Add Picture screen: upload a file or point at a URL
New screen alongside clock/weather/navidrome. Upload goes through the web UI (validated with PIL before accepting, stored under web/static/uploads/, gitignored), or point it at a URL re-fetched every 30 minutes. Cover/contain fit modes (new fit_cover/fit_contain helpers in render_utils.py, generalized from the square-only cover-art cropping already used by the Navidrome screen), plus the same contrast-boost/grayscale options that screen has.
This commit is contained in:
1 parent
61348be674
commit
bfe4616f20
8 files changed
+232
-4
No files matched your search
+55
@@ -2,10 +2,17 @@
|
||||
|
||||
import requests
|
||||
from flask import Flask, redirect, render_template, request, url_for
|
||||
from PIL import Image
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from screens.picture import UPLOAD_DIR
|
||||
|
||||
ALLOWED_IMAGE_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "bmp", "webp"}
|
||||
|
||||
|
||||
def create_app(config, screens: dict, scheduler) -> Flask:
|
||||
app = Flask(__name__)
|
||||
app.config["MAX_CONTENT_LENGTH"] = 20 * 1024 * 1024 # 20 MB, generous for a photo
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
@@ -78,6 +85,54 @@ def create_app(config, screens: dict, scheduler) -> Flask:
|
||||
|
||||
return render_template("settings_weather.html", settings=config.get_screen_settings("weather"), error=error)
|
||||
|
||||
@app.route("/settings/picture", methods=["GET", "POST"])
|
||||
def settings_picture():
|
||||
error = None
|
||||
if request.method == "POST":
|
||||
url = request.form.get("image_url", "").strip()
|
||||
common_updates = {
|
||||
"fit": request.form.get("fit", "cover"),
|
||||
"boost_contrast": "boost_contrast" in request.form,
|
||||
"grayscale": "grayscale" in request.form,
|
||||
}
|
||||
uploaded = request.files.get("image")
|
||||
|
||||
if uploaded and uploaded.filename:
|
||||
filename = secure_filename(uploaded.filename)
|
||||
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
|
||||
if ext not in ALLOWED_IMAGE_EXTENSIONS:
|
||||
error = "Unsupported file type -- use PNG, JPG, GIF, BMP, or WebP."
|
||||
else:
|
||||
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||
# Only one picture is ever active -- clear any previous
|
||||
# upload first so a different extension doesn't leave an
|
||||
# orphaned file behind.
|
||||
for old in UPLOAD_DIR.glob("current.*"):
|
||||
old.unlink()
|
||||
dest = UPLOAD_DIR / f"current.{ext}"
|
||||
uploaded.save(dest)
|
||||
try:
|
||||
Image.open(dest).verify()
|
||||
except Exception:
|
||||
dest.unlink(missing_ok=True)
|
||||
error = "That file doesn't look like a valid image."
|
||||
else:
|
||||
config.update_screen_settings("picture", {
|
||||
"mode": "upload",
|
||||
"uploaded_filename": dest.name,
|
||||
**common_updates,
|
||||
})
|
||||
return redirect(url_for("settings_picture", saved=1))
|
||||
elif url:
|
||||
config.update_screen_settings("picture", {"mode": "url", "image_url": url, **common_updates})
|
||||
return redirect(url_for("settings_picture", saved=1))
|
||||
else:
|
||||
# Neither a new upload nor a URL change -- just update fit/boost/grayscale.
|
||||
config.update_screen_settings("picture", common_updates)
|
||||
return redirect(url_for("settings_picture", saved=1))
|
||||
|
||||
return render_template("settings_picture.html", settings=config.get_screen_settings("picture"), error=error)
|
||||
|
||||
@app.route("/settings/rotation", methods=["GET", "POST"])
|
||||
def settings_rotation():
|
||||
if request.method == "POST":
|
||||
|
||||
Reference in new issue
Block a user