feat: fit-contain letterbox + smart queue refill
- Replace center-crop with fit-contain (no photo cropping) - Letterbox bars filled with adjacent edge average color - Queue fetches in batches until 50 usable assets - Auto-refill queue on exhaustion, dedup via shown history - Filter RAW files from asset selection Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "photo_queue.h"
|
||||
#include "immich_client.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <algorithm>
|
||||
#include "config.h"
|
||||
|
||||
void PhotoQueue::begin(ImmichClient& client, SettingsManager& settings) {
|
||||
@@ -14,37 +15,45 @@ void PhotoQueue::begin(ImmichClient& client, SettingsManager& settings) {
|
||||
bool PhotoQueue::sync() {
|
||||
if (_client == nullptr || _settings == nullptr) return false;
|
||||
|
||||
_lastSyncAttempt = millis();
|
||||
|
||||
Settings s = _settings->get();
|
||||
|
||||
// Fetch in batches until we have TARGET_QUEUE_SIZE usable, non-duplicate IDs
|
||||
std::vector<String> newIds;
|
||||
int rounds = 0;
|
||||
|
||||
// Get selected album IDs
|
||||
std::vector<String> albumIds = getSelectedAlbumIds();
|
||||
Serial.printf("[queue] Fetching until %d usable assets (excluding %d shown)\n",
|
||||
TARGET_QUEUE_SIZE, _shown.size());
|
||||
|
||||
if (albumIds.empty()) {
|
||||
// "All photos" mode — fetch from all albums
|
||||
auto albums = _client->fetchAlbums();
|
||||
for (auto& album : albums) {
|
||||
auto ids = _client->fetchAlbumAssetIds(album.id);
|
||||
for (auto& id : ids) {
|
||||
newIds.push_back(id);
|
||||
}
|
||||
while (newIds.size() < TARGET_QUEUE_SIZE && rounds < MAX_FETCH_ROUNDS) {
|
||||
auto batch = _client->fetchRandomAssetIds(50);
|
||||
if (batch.empty()) {
|
||||
Serial.printf("[queue] Fetch round %d returned empty\n", rounds + 1);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Fetch from selected albums only
|
||||
for (auto& albumId : albumIds) {
|
||||
auto ids = _client->fetchAlbumAssetIds(albumId);
|
||||
for (auto& id : ids) {
|
||||
newIds.push_back(id);
|
||||
}
|
||||
|
||||
for (auto& id : batch) {
|
||||
if (newIds.size() >= TARGET_QUEUE_SIZE) break;
|
||||
|
||||
// Skip if already in this batch
|
||||
if (std::find(newIds.begin(), newIds.end(), id) != newIds.end()) continue;
|
||||
|
||||
// Skip if recently shown
|
||||
if (std::find(_shown.begin(), _shown.end(), id) != _shown.end()) continue;
|
||||
|
||||
newIds.push_back(id);
|
||||
}
|
||||
|
||||
rounds++;
|
||||
Serial.printf("[queue] Round %d: have %d/%d usable assets\n",
|
||||
rounds, newIds.size(), TARGET_QUEUE_SIZE);
|
||||
}
|
||||
|
||||
// Deduplicate
|
||||
std::sort(newIds.begin(), newIds.end());
|
||||
newIds.erase(std::unique(newIds.begin(), newIds.end()), newIds.end());
|
||||
|
||||
if (newIds.empty()) {
|
||||
Serial.println("[queue] No assets found");
|
||||
_syncRetryDelay = min(_syncRetryDelay * 2, SYNC_RETRY_MAX);
|
||||
Serial.printf("[queue] Sync failed after %d rounds, retry in %lus\n",
|
||||
rounds, _syncRetryDelay / 1000);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -68,24 +77,34 @@ bool PhotoQueue::sync() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Clamp cursor
|
||||
if (_cursor >= _queue.size()) {
|
||||
_cursor = 0;
|
||||
}
|
||||
// Reset cursor for fresh queue
|
||||
_cursor = 0;
|
||||
|
||||
_lastSyncTime = millis();
|
||||
Serial.printf("[queue] Synced: %d assets, cursor at %d\n", _queue.size(), _cursor);
|
||||
_syncRetryDelay = SYNC_RETRY_MIN;
|
||||
Serial.printf("[queue] Synced: %d assets in %d rounds, cursor at %d\n",
|
||||
_queue.size(), rounds, _cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
String PhotoQueue::next() {
|
||||
if (_queue.empty()) return "";
|
||||
if (_cursor >= _queue.size()) {
|
||||
// Auto-refill when queue is exhausted
|
||||
if (_queue.empty() || _cursor >= _queue.size()) {
|
||||
Serial.println("[queue] Queue exhausted, fetching new batch");
|
||||
sync();
|
||||
_cursor = 0;
|
||||
shuffle(); // Reshuffle on wrap for random mode
|
||||
}
|
||||
|
||||
if (_queue.empty()) return "";
|
||||
|
||||
String id = _queue[_cursor++];
|
||||
|
||||
// Track shown IDs for dedup on next refill
|
||||
_shown.push_back(id);
|
||||
if (_shown.size() > MAX_SHOWN_HISTORY) {
|
||||
_shown.erase(_shown.begin());
|
||||
}
|
||||
|
||||
// Persist cursor
|
||||
_settings->saveField("queue_cursor", static_cast<uint32_t>(_cursor));
|
||||
|
||||
@@ -109,17 +128,21 @@ size_t PhotoQueue::size() {
|
||||
}
|
||||
|
||||
bool PhotoQueue::needsResync() {
|
||||
if (_lastSyncTime == 0) return true;
|
||||
unsigned long elapsed = millis() - _lastSyncTime;
|
||||
unsigned long now = millis();
|
||||
|
||||
// If never synced successfully, use retry backoff
|
||||
if (_lastSyncTime == 0) {
|
||||
if (_lastSyncAttempt == 0) return true;
|
||||
return (now - _lastSyncAttempt) >= _syncRetryDelay;
|
||||
}
|
||||
|
||||
// Normal resync interval
|
||||
unsigned long elapsed = now - _lastSyncTime;
|
||||
return elapsed >= (QUEUE_RESYNC_HOURS * 3600000UL);
|
||||
}
|
||||
|
||||
String PhotoQueue::findPortraitPair(size_t startIndex) {
|
||||
// Look ahead up to PORTRAIT_LOOKAHEAD items for another portrait
|
||||
for (size_t i = 1; i <= PORTRAIT_LOOKAHEAD && (startIndex + i) < _queue.size(); i++) {
|
||||
// We'd need asset info to determine portrait status
|
||||
// This will be called by the display task which fetches AssetInfo
|
||||
// Return the ID — caller checks isPortrait from AssetInfo
|
||||
return _queue[startIndex + i];
|
||||
}
|
||||
return "";
|
||||
@@ -133,9 +156,6 @@ void PhotoQueue::shuffle() {
|
||||
}
|
||||
|
||||
void PhotoQueue::sortChronological(bool reverse) {
|
||||
// For chronological sort, we'd need timestamps which we don't store in the queue
|
||||
// The IDs from Immich are UUIDs, not sortable by time
|
||||
// For now, keep the order returned by Immich (which is chronological within albums)
|
||||
if (reverse) {
|
||||
std::reverse(_queue.begin(), _queue.end());
|
||||
}
|
||||
@@ -148,9 +168,7 @@ void PhotoQueue::applyFavoritesWeighting() {
|
||||
if (s.cycle_mode != CycleMode::FavoritesWeighted) return;
|
||||
|
||||
auto favorites = _client->fetchFavoriteAssetIds();
|
||||
// Add favorites 2 more times (total 3× appearance)
|
||||
for (auto& fav : favorites) {
|
||||
// Only add if already in queue
|
||||
bool inQueue = false;
|
||||
for (auto& id : _queue) {
|
||||
if (id == fav) { inQueue = true; break; }
|
||||
|
||||
Reference in New Issue
Block a user