From e2d26ebdea86c8157017602b64fc4019fb24c842 Mon Sep 17 00:00:00 2001 From: Saurav Sharma <77329690+saurav61091@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:07:05 +0530 Subject: [PATCH] fix(web): prevent Safari from overwriting live photo image with video (#26898) When downloading a live photo, Safari overwrites the image file with the motion video because both share the same base filename. Append '-motion' suffix to the video filename to prevent collision. For example, IMG_1234.heic and IMG_1234.mov become IMG_1234.heic and IMG_1234-motion.mov. Fixes #23055 --- web/src/lib/services/asset.service.spec.ts | 2 +- web/src/lib/services/asset.service.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/web/src/lib/services/asset.service.spec.ts b/web/src/lib/services/asset.service.spec.ts index 170f5d2f23..b63df86479 100644 --- a/web/src/lib/services/asset.service.spec.ts +++ b/web/src/lib/services/asset.service.spec.ts @@ -78,7 +78,7 @@ describe('AssetService', () => { const asset = assetFactory.build({ originalFileName: 'asset.heic', livePhotoVideoId: '1' }); await handleDownloadAsset(asset, { edited: false }); expect($t).toHaveBeenNthCalledWith(1, 'downloading_asset_filename', { values: { filename: 'asset.heic' } }); - expect($t).toHaveBeenNthCalledWith(2, 'downloading_asset_filename', { values: { filename: 'asset.mov' } }); + expect($t).toHaveBeenNthCalledWith(2, 'downloading_asset_filename', { values: { filename: 'asset-motion.mov' } }); expect(toastManager.primary).toHaveBeenCalledWith('formatter'); }); }); diff --git a/web/src/lib/services/asset.service.ts b/web/src/lib/services/asset.service.ts index baade8facd..bc16b65577 100644 --- a/web/src/lib/services/asset.service.ts +++ b/web/src/lib/services/asset.service.ts @@ -319,8 +319,14 @@ export const handleDownloadAsset = async (asset: AssetResponseDto, { edited }: { if (asset.livePhotoVideoId) { const motionAsset = await getAssetInfo({ ...authManager.params, id: asset.livePhotoVideoId }); if (!isAndroidMotionVideo(motionAsset) || get(preferences)?.download.includeEmbeddedVideos) { + const motionFilename = motionAsset.originalFileName; + const lastDotIndex = motionFilename.lastIndexOf('.'); + const motionDownloadFilename = + lastDotIndex > 0 + ? `${motionFilename.slice(0, lastDotIndex)}-motion${motionFilename.slice(lastDotIndex)}` + : `${motionFilename}-motion`; assets.push({ - filename: motionAsset.originalFileName, + filename: motionDownloadFilename, id: asset.livePhotoVideoId, cacheKey: motionAsset.thumbhash, });