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:
107
src/main.cpp
107
src/main.cpp
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <M5Unified.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
@@ -47,6 +48,11 @@ void displayTask(void* param) {
|
||||
|
||||
if (photoQueue.sync()) {
|
||||
Serial.printf("[display_task] Queue ready: %d photos\n", photoQueue.size());
|
||||
// Trigger first photo immediately
|
||||
if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
refreshRequested = true;
|
||||
xSemaphoreGive(stateMutex);
|
||||
}
|
||||
} else {
|
||||
displayManager.showMessage("No Photos", "Select albums in web UI");
|
||||
}
|
||||
@@ -80,16 +86,22 @@ void displayTask(void* param) {
|
||||
}
|
||||
|
||||
if (shouldRefresh && photoQueue.size() > 0 && wifiManager.isConnected()) {
|
||||
// Get next photo ID
|
||||
String assetId;
|
||||
if (wantsRandom) {
|
||||
assetId = photoQueue.random();
|
||||
} else {
|
||||
assetId = photoQueue.next();
|
||||
}
|
||||
// Try up to 3 assets in case some fail to decode
|
||||
static constexpr int MAX_RETRIES = 10;
|
||||
bool displayed = false;
|
||||
|
||||
if (assetId.length() > 0) {
|
||||
Serial.printf("[display_task] Loading asset: %s\n", assetId.c_str());
|
||||
for (int attempt = 0; attempt < MAX_RETRIES && !displayed; attempt++) {
|
||||
String assetId;
|
||||
if (wantsRandom) {
|
||||
assetId = photoQueue.random();
|
||||
} else {
|
||||
assetId = photoQueue.next();
|
||||
}
|
||||
|
||||
if (assetId.length() == 0) break;
|
||||
|
||||
Serial.printf("[display_task] Loading asset: %s (attempt %d)\n",
|
||||
assetId.c_str(), attempt + 1);
|
||||
|
||||
// Fetch asset info for portrait detection and metadata
|
||||
AssetInfo info = immichClient.fetchAssetInfo(assetId);
|
||||
@@ -100,55 +112,37 @@ void displayTask(void* param) {
|
||||
bool downloaded = immichClient.downloadAsset(
|
||||
assetId, s.img_quality, &jpegBuf, &jpegSize);
|
||||
|
||||
if (downloaded && jpegBuf != nullptr) {
|
||||
ProcessedImage img;
|
||||
|
||||
if (info.isPortrait) {
|
||||
// Try to find a portrait pair
|
||||
String pairId = photoQueue.findPortraitPair(
|
||||
settingsManager.get().queue_cursor);
|
||||
if (pairId.length() > 0) {
|
||||
AssetInfo pairInfo = immichClient.fetchAssetInfo(pairId);
|
||||
if (pairInfo.isPortrait) {
|
||||
uint8_t* jpeg2Buf = nullptr;
|
||||
size_t jpeg2Size = 0;
|
||||
if (immichClient.downloadAsset(pairId, s.img_quality,
|
||||
&jpeg2Buf, &jpeg2Size)) {
|
||||
img = imagePipeline.processPortraitPair(
|
||||
jpegBuf, jpegSize, jpeg2Buf, jpeg2Size);
|
||||
free(jpeg2Buf);
|
||||
} else {
|
||||
img = imagePipeline.process(jpegBuf, jpegSize);
|
||||
}
|
||||
} else {
|
||||
img = imagePipeline.process(jpegBuf, jpegSize);
|
||||
}
|
||||
} else {
|
||||
img = imagePipeline.process(jpegBuf, jpegSize);
|
||||
}
|
||||
} else {
|
||||
img = imagePipeline.process(jpegBuf, jpegSize);
|
||||
}
|
||||
|
||||
free(jpegBuf);
|
||||
|
||||
if (img.valid) {
|
||||
displayManager.showImage(img);
|
||||
// Show metadata overlay if enabled
|
||||
if (s.meta_flags != 0) {
|
||||
displayManager.showMetadata(info, s.meta_flags, s.meta_pos);
|
||||
}
|
||||
imagePipeline.freeImage(img);
|
||||
} else {
|
||||
Serial.println("[display_task] Image processing failed");
|
||||
}
|
||||
if (!downloaded || jpegBuf == nullptr) {
|
||||
Serial.println("[display_task] Download failed, trying next");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
lastRefreshTime = millis();
|
||||
xSemaphoreGive(stateMutex);
|
||||
Serial.printf("[display_task] JPEG: %u KB, PSRAM free: %u KB\n",
|
||||
(unsigned)(jpegSize / 1024),
|
||||
(unsigned)(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024));
|
||||
|
||||
ProcessedImage img = imagePipeline.process(jpegBuf, jpegSize);
|
||||
free(jpegBuf);
|
||||
|
||||
Serial.printf("[display_task] Post-process PSRAM free: %u KB\n",
|
||||
(unsigned)(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024));
|
||||
|
||||
if (img.valid) {
|
||||
displayManager.showImage(img);
|
||||
if (s.meta_flags != 0) {
|
||||
displayManager.showMetadata(info, s.meta_flags, s.meta_pos);
|
||||
}
|
||||
imagePipeline.freeImage(img);
|
||||
displayed = true;
|
||||
} else {
|
||||
Serial.println("[display_task] Processing failed, trying next");
|
||||
}
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
lastRefreshTime = millis();
|
||||
xSemaphoreGive(stateMutex);
|
||||
}
|
||||
}
|
||||
|
||||
// Yield — check every second
|
||||
@@ -181,6 +175,11 @@ void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.println("[main] Immich Frame v1.0 booting...");
|
||||
Serial.printf("[main] PSRAM: %u KB free / %u KB total\n",
|
||||
(unsigned)(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024),
|
||||
(unsigned)(heap_caps_get_total_size(MALLOC_CAP_SPIRAM) / 1024));
|
||||
Serial.printf("[main] Internal RAM: %u KB free\n",
|
||||
(unsigned)(heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024));
|
||||
|
||||
// Create state mutex
|
||||
stateMutex = xSemaphoreCreateMutex();
|
||||
|
||||
Reference in New Issue
Block a user