diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index 93ceeb66f1..ad582a5d25 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -37,7 +37,7 @@ import { import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto'; import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; -import { timeUtils } from '@app/common/utils'; +import { assetUtils, timeUtils } from '@app/common/utils'; import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; import { UpdateAssetDto } from './dto/update-asset.dto'; @@ -456,7 +456,7 @@ export class AssetService { await fs.access(videoPath, constants.R_OK | constants.W_OK); - if (query.isWeb && asset.mimeType == 'video/quicktime') { + if (query.isWeb && !assetUtils.isWebPlayable(asset.mimeType)) { videoPath = asset.encodedVideoPath == '' ? String(asset.originalPath) : String(asset.encodedVideoPath); mimeType = asset.encodedVideoPath == '' ? asset.mimeType : 'video/mp4'; } diff --git a/server/apps/immich/src/config/asset-upload.config.spec.ts b/server/apps/immich/src/config/asset-upload.config.spec.ts index 9d7d4e70d8..9e76ceb853 100644 --- a/server/apps/immich/src/config/asset-upload.config.spec.ts +++ b/server/apps/immich/src/config/asset-upload.config.spec.ts @@ -60,6 +60,12 @@ describe('assetUploadOption', () => { expect(callback).toHaveBeenCalledWith(null, true); }); + it('should allow webm videos', async () => { + const file = { mimetype: 'video/webm', originalname: 'test.webm' } as any; + fileFilter(mock.userRequest, file, callback); + expect(callback).toHaveBeenCalledWith(null, true); + }); + it('should not allow unknown types', async () => { const file = { mimetype: 'application/html', originalname: 'test.html' } as any; const callback = jest.fn(); diff --git a/server/apps/immich/src/config/asset-upload.config.ts b/server/apps/immich/src/config/asset-upload.config.ts index dcf8dfb6a7..cbbb3a2c3d 100644 --- a/server/apps/immich/src/config/asset-upload.config.ts +++ b/server/apps/immich/src/config/asset-upload.config.ts @@ -28,7 +28,7 @@ function fileFilter(req: Request, file: any, cb: any) { } if ( file.mimetype.match( - /\/(jpg|jpeg|png|gif|mp4|x-msvideo|quicktime|heic|heif|dng|x-adobe-dng|webp|tiff|3gpp|nef|x-nikon-nef)$/, + /\/(jpg|jpeg|png|gif|mp4|webm|x-msvideo|quicktime|heic|heif|dng|x-adobe-dng|webp|tiff|3gpp|nef|x-nikon-nef)$/, ) ) { cb(null, true); diff --git a/server/libs/common/src/utils/asset-utils.spec.ts b/server/libs/common/src/utils/asset-utils.spec.ts new file mode 100644 index 0000000000..9fa8f04c92 --- /dev/null +++ b/server/libs/common/src/utils/asset-utils.spec.ts @@ -0,0 +1,20 @@ +import { assetUtils } from './asset-utils'; + +describe('Asset Utilities', () => { + describe('isWebPlayable', () => { + it('Check that it returns true with mimetype webm', () => { + const result = assetUtils.isWebPlayable('video/webm'); + expect(result).toBeTruthy(); + }); + + it('Check that returns true with mimetype mp4', () => { + const result = assetUtils.isWebPlayable('video/mp4'); + expect(result).toBeTruthy(); + }); + + it('Check that returns false with mimetype quicktime', () => { + const result = assetUtils.isWebPlayable('video/quicktime'); + expect(result).toBeFalsy(); + }); + }); +}); diff --git a/server/libs/common/src/utils/asset-utils.ts b/server/libs/common/src/utils/asset-utils.ts index f130c49548..8bf7027639 100644 --- a/server/libs/common/src/utils/asset-utils.ts +++ b/server/libs/common/src/utils/asset-utils.ts @@ -36,4 +36,13 @@ const deleteFiles = (asset: AssetEntity | AssetResponseDto) => { } }; -export const assetUtils = { deleteFiles }; +const isWebPlayable = (mimeType: string | null): boolean => { + const WEB_PLAYABLE = ['video/webm', 'video/mp4']; + + if (mimeType !== null) { + return WEB_PLAYABLE.includes(mimeType); + } + return false; +}; + +export const assetUtils = { deleteFiles, isWebPlayable };