summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Keller <tjk@tjkeller.xyz>2025-06-15 15:06:34 -0500
committerTim Keller <tjk@tjkeller.xyz>2025-06-15 15:06:34 -0500
commitcd1657ece1fa199964abd6544b81b394ab9369aa (patch)
tree5b9448f8fb720d5be4ae8105ff543cdff0fe047a
parente1a6fc09afc088dcb67263ed5923f5be41c32c31 (diff)
downloadimmich-frame-cd1657ece1fa199964abd6544b81b394ab9369aa.tar.xz
immich-frame-cd1657ece1fa199964abd6544b81b394ab9369aa.zip
callbacks on lctl, websocket controls
-rw-r--r--flaskapi.py24
-rw-r--r--lazycachelist.py51
-rw-r--r--pix.py5
-rw-r--r--requirements.txt2
4 files changed, 60 insertions, 22 deletions
diff --git a/flaskapi.py b/flaskapi.py
index 0791016..a537192 100644
--- a/flaskapi.py
+++ b/flaskapi.py
@@ -1,7 +1,9 @@
-from flask import Flask, Blueprint, request, send_from_directory, send_file, abort
+from flask import Flask, Blueprint, request, send_from_directory, send_file, abort, redirect
+from flask_socketio import SocketIO, emit
from flask_cors import CORS
app = Flask(__name__, static_folder="static/dist", static_url_path="/")
+socketio = SocketIO(app, cors_allowed_origins="*") # TODO remove later
@app.route("/")
@app.route("/slideshow")
@@ -13,11 +15,11 @@ def home():
api = Blueprint("api", __name__)
CORS(api, origins="*") # For debugging TODO remove later
-@api.route("/seek")
-def seek():
+#@api.route("/seek")
+@socketio.on("seek")
+def seek(increment):
pd = app.config["pix_display"]
- increment = request.args.get("increment", default=1, type=int)
pd.queue.put(lambda: pd.seek(increment))
while not pd.queue.empty():
pass
@@ -26,7 +28,7 @@ def seek():
"imageIndex": pd.current_texture_index,
}
-@api.route("/albums/get")
+@api.route("/albums")
def get_albums():
ic = app.config["immich_connector"]
keys = [ "albumName", "albumThumbnailAssetId", "id", "startDate", "endDate", "assetCount", "shared", ]
@@ -34,14 +36,20 @@ def get_albums():
key: album[key] for key in keys
} for album in ic.load_all_albums() ]
-@api.route("/albums/thumb/<key>")
-def get_album_thumb(key):
+@api.route("/asset/<key>/thumbnail", defaults={ "size": "thumbnail" })
+@api.route("/asset/<key>", defaults={ "size": "preview" })
+def get_asset(key, size):
# TODO ensure getting actual album thumb
ic = app.config["immich_connector"]
- image_data, mimetype = ic.load_image(key, size="thumbnail")
+ image_data, mimetype = ic.load_image(key, size=size)
if image_data is None:
abort(400)
return send_file(image_data, mimetype=mimetype)
+@api.route("/redirect/<path:path>")
+def immich_redirect(path):
+ ic = app.config["immich_connector"]
+ return redirect(f"{ic.server_url}/{path}")
+
app.register_blueprint(api, url_prefix="/api")
diff --git a/lazycachelist.py b/lazycachelist.py
index 0c37ba3..b08b85d 100644
--- a/lazycachelist.py
+++ b/lazycachelist.py
@@ -1,4 +1,4 @@
-from dataclasses import dataclass
+from dataclasses import dataclass, asdict
from texture import ImageTextureImmichAsset
@@ -14,8 +14,27 @@ class Album:
return end - start
+@dataclass
+class CallbackStateData:
+ asset_index: int
+ movement: int
+ assets: list[str]
+
+ @classmethod
+ def from_lctl(cls, l):
+ si = (l.asset_index - 5) % l.asset_count
+ ei = (l.asset_index + 6) % l.asset_count
+ sa = l.assets[si:ei] if si < ei else l.assets[si:] + l.assets[:ei]
+ assets = [ a["id"] for a in sa ]
+ return cls(
+ asset_index=l.asset_index,
+ movement=l.last_movement,
+ assets=assets,
+ )
+
+
class LazyCachingTextureList():
- def __init__(self, immich_connector, album_ids, max_cache_items=100):
+ def __init__(self, immich_connector, album_ids, max_cache_items=100, change_callback=None):
self.immich_connector = immich_connector
assert max_cache_items >= 20, "Minimum cache items is 20" # Double small radius
@@ -29,11 +48,16 @@ class LazyCachingTextureList():
self.cache_index = 0
self.asset_index = 0
self.asset_count = 0
+ self.last_movement = 0
self.cached_items = 0
self.album_keys = album_ids
+ # TODO simplify album handling, dont need classes, etc.
self.albums = self._get_albums()
+ self.assets = self._get_album_assets()
+
+ self.change_callback = change_callback
def index_in_cache_range(self, index):
index_range_low = (self.asset_index - self.cache_items_behind) % self.asset_count
@@ -54,18 +78,13 @@ class LazyCachingTextureList():
albums.append(Album(id, i, i + asset_count))
i += asset_count + 1
self.asset_count += asset_count
- #self.asset_count = sum(( album.assets_count for album in self.albums ))
return albums
- def _get_album_asset_by_index(self, asset_index):
- if asset_index < 0:
- asset_index = asset_count - asset_index
+ def _get_album_assets(self):
+ assets = []
for album in self.albums:
- if album.range_start <= asset_index <= album.range_end:
- if album.assets_list is None:
- album.assets_list = self.immich_connector.load_album_assets(album.id)
- return album.assets_list[asset_index - album.range_start]
- raise IndexError("Index out of bounds")
+ assets += self.immich_connector.load_album_assets(album.id)
+ return assets
def _fill_cache(self, current_radius, final_radius, step):
if current_radius >= final_radius:
@@ -76,7 +95,7 @@ class LazyCachingTextureList():
asset_index = (self.asset_index + i) % self.asset_count
if self.cache[cache_index]:
self.cache[cache_index].free() # Since this is a ring buffer, textures outside of range can just get free'd here
- asset = self._get_album_asset_by_index(asset_index)
+ asset = self.assets[asset_index]
tex = ImageTextureImmichAsset(asset, asset_index)
self.immich_connector.load_texture_async(self, tex)
self.cache[cache_index] = tex
@@ -87,6 +106,8 @@ class LazyCachingTextureList():
def _update_cache_get_item(self, asset_index):
prev_asset_index = self.asset_index
self.asset_index = asset_index % self.asset_count
+ #if prev_asset_index == asset_index:
+ # return self.cache[self.cache_index]
# Movement is the distance between the previous and current index in the assets list
# Since the list wraps around, fastest method is just to get the abs min of the 3 cases
movement = min(
@@ -94,6 +115,7 @@ class LazyCachingTextureList():
asset_index - self.asset_count, # Wrap backwards (0 -> -1)
self.asset_count - prev_asset_index, # Wrap forwards (-1 -> 0)
key=abs)
+ self.last_movement = movement
self.cache_index = (self.cache_index + movement) % self.cache_length
ahead = max(0, self.cache_items_ahead - movement)
@@ -114,6 +136,11 @@ class LazyCachingTextureList():
self.cache_items_ahead = ahead
self.cache_items_behind = behind
+
+ # Perform callback
+ if prev_asset_index != asset_index and self.change_callback:
+ self.change_callback(asdict(CallbackStateData.from_lctl(self)))
+
return self.cache[self.cache_index]
def __len__(self):
diff --git a/pix.py b/pix.py
index 4064892..45e3f70 100644
--- a/pix.py
+++ b/pix.py
@@ -6,7 +6,7 @@ from OpenGL.GLUT import glutLeaveMainLoop
from lazycachelist import LazyCachingTextureList
from window import PixDisplay
from immich import ImmichConnector
-from flaskapi import app
+from flaskapi import app, socketio
def handle_sigint(sig, frame):
@@ -22,7 +22,7 @@ def handle_sigint(sig, frame):
if __name__ == "__main__":
immich_connector = ImmichConnector("http://192.168.1.13", "m5nqOoBc4uhAba21gZdCP3z8D3JT4GPxDXL2psd52EA")
album_keys = [ "38617851-6b57-44f1-b5f7-82577606afc4" ]
- lazy_texture_list = LazyCachingTextureList(immich_connector, album_keys, 30)
+ lazy_texture_list = LazyCachingTextureList(immich_connector, album_keys, 30, lambda d: socketio.emit("seek", d))
pd = PixDisplay(lazy_texture_list)
t1 = Thread(target=immich_connector.idle, daemon=True)
@@ -30,6 +30,7 @@ if __name__ == "__main__":
app.config["pix_display"] = pd
app.config["immich_connector"] = immich_connector
+ app.config["textures"] = lazy_texture_list
flask_thread = Thread(target=app.run, daemon=True, kwargs={ "port": 5000 })
flask_thread.start()
diff --git a/requirements.txt b/requirements.txt
index 7d9001a..991c1ce 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,6 @@
flask
+flask-cors # DEBUG
+flask-socketio
numpy
pillow
pyopengl