diff options
| author | Tim Keller <tjk@tjkeller.xyz> | 2025-06-15 15:06:34 -0500 |
|---|---|---|
| committer | Tim Keller <tjk@tjkeller.xyz> | 2025-06-15 15:06:34 -0500 |
| commit | cd1657ece1fa199964abd6544b81b394ab9369aa (patch) | |
| tree | 5b9448f8fb720d5be4ae8105ff543cdff0fe047a | |
| parent | e1a6fc09afc088dcb67263ed5923f5be41c32c31 (diff) | |
| download | immich-frame-cd1657ece1fa199964abd6544b81b394ab9369aa.tar.xz immich-frame-cd1657ece1fa199964abd6544b81b394ab9369aa.zip | |
callbacks on lctl, websocket controls
| -rw-r--r-- | flaskapi.py | 24 | ||||
| -rw-r--r-- | lazycachelist.py | 51 | ||||
| -rw-r--r-- | pix.py | 5 | ||||
| -rw-r--r-- | requirements.txt | 2 |
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): @@ -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 |
