From 1963573db41c644be9325a4bbe30f3a46eb55770 Mon Sep 17 00:00:00 2001 From: jubnl Date: Fri, 17 Apr 2026 15:35:42 +0200 Subject: [PATCH] fix(synology): use Thumbnail API with size xl for originals to avoid HEIC Replace SYNO.Foto.Download with SYNO.Foto.Thumbnail (size=xl) for the original kind, mirroring the Immich approach. Synology's download endpoint returns the raw file (HEIC for iPhone photos), while the Thumbnail API always serves a browser-compatible JPEG render. --- .../src/services/memories/synologyService.ts | 35 ++++++++----------- .../integration/memories-synology.test.ts | 10 ------ 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/server/src/services/memories/synologyService.ts b/server/src/services/memories/synologyService.ts index c63e7d2f..d493b960 100644 --- a/server/src/services/memories/synologyService.ts +++ b/server/src/services/memories/synologyService.ts @@ -637,27 +637,20 @@ export async function streamSynologyAsset( //size: 'sm' 240px| 'm' 320px| 'xl' 1280px| 'preview' ? - const resolvedSize = size || 'sm'; - const params = kind === 'thumbnail' - ? new URLSearchParams({ - api: 'SYNO.Foto.Thumbnail', - method: 'get', - version: '2', - mode: 'download', - id: parsedId.id, - type: 'unit', - size: resolvedSize, - cache_key: parsedId.cacheKey, - _sid: sid.data, - }) - : new URLSearchParams({ - api: 'SYNO.Foto.Download', - method: 'download', - version: '2', - cache_key: parsedId.cacheKey, - unit_id: `[${parsedId.id}]`, - _sid: sid.data, - }); + // Use Thumbnail API for both thumbnail and original — avoids serving raw HEIC files + // (original uses xl size to get a full-resolution JPEG-compatible render) + const resolvedSize = kind === 'original' ? 'xl' : (size || 'sm'); + const params = new URLSearchParams({ + api: 'SYNO.Foto.Thumbnail', + method: 'get', + version: '2', + mode: 'download', + id: parsedId.id, + type: 'unit', + size: resolvedSize, + cache_key: parsedId.cacheKey, + _sid: sid.data, + }); if (passphrase) params.append('passphrase', passphrase); const url = _buildSynologyEndpoint(synology_credentials.data.synology_url, params.toString()); diff --git a/server/tests/integration/memories-synology.test.ts b/server/tests/integration/memories-synology.test.ts index aa18b133..d5880310 100644 --- a/server/tests/integration/memories-synology.test.ts +++ b/server/tests/integration/memories-synology.test.ts @@ -166,16 +166,6 @@ vi.mock('../../src/utils/ssrfGuard', async () => { }); } - // Original download - if (apiName === 'SYNO.Foto.Download') { - const imageBytes = Buffer.from('fake-synology-original'); - return Promise.resolve({ - ok: true, status: 200, - headers: { get: (h: string) => h === 'content-type' ? 'image/jpeg' : null }, - body: new ReadableStream({ start(c) { c.enqueue(imageBytes); c.close(); } }), - }); - } - return Promise.reject(new Error(`Unexpected safeFetch call to Synology: ${u}, api=${apiName}`)); }