From 3cc77d945b476db5e8bc24c8235bb99b7ef34186 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Thu, 6 Jul 2023 11:27:21 -0400 Subject: [PATCH] fix(server): use thumbnail content type instead of application/octet-stream (#3075) * asset mimetype instead of application/octet-stream * use thumbnail mimetype instead * narrowed openapi spec * thumbnail format validation * JPEG fallback, `getThumbnailPath` returns format * return content type in `getThumbnailPath` * moved `format` validation to dto * removed unused import * moved fallback warning --- mobile/openapi/doc/AssetApi.md | Bin 56109 -> 56107 bytes mobile/openapi/doc/PersonApi.md | Bin 10296 -> 10282 bytes server/immich-openapi-specs.json | 10 ++++++++-- .../immich/api-v1/asset/asset.controller.ts | 4 +--- .../src/immich/api-v1/asset/asset.service.ts | 13 +++++++------ .../asset/dto/get-asset-thumbnail.dto.ts | 3 ++- .../immich/controllers/person.controller.ts | 3 +-- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 319deb2acfff3bd01f7dbd55b5aedbc24d53a552..c71b4151d3adf98aef58c2fbed5757cba6e74eaf 100644 GIT binary patch delta 41 tcmZ3xjd}Gp<_)sdVwt&#>8biz1*z#e3J`XAYEr@Ehnx3o{#b1j4**`g5y}7n delta 26 icmZ3zjd|@h<_)sdlOJUBOct;@J-MYpU^7#VW;_6&s|y7H diff --git a/mobile/openapi/doc/PersonApi.md b/mobile/openapi/doc/PersonApi.md index dd1c0eb8e41bfe56159ddd2de18e166253d5cd69..aa37a294e1ad91c12d10211cd658a7d30b534080 100644 GIT binary patch delta 27 icmdlHuqt4Ki9AepW$h`s4=n9h+GcOgI3R6$$MC delta 29 kcmZ1#up?lDiTvbV4j!KT)WqD)cJgmH0I7TnA^-pY diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index caae449e25..5f664730f0 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1673,7 +1673,13 @@ "responses": { "200": { "content": { - "application/octet-stream": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "image/webp": { "schema": { "type": "string", "format": "binary" @@ -2704,7 +2710,7 @@ "responses": { "200": { "content": { - "application/octet-stream": { + "image/jpeg": { "schema": { "type": "string", "format": "binary" diff --git a/server/src/immich/api-v1/asset/asset.controller.ts b/server/src/immich/api-v1/asset/asset.controller.ts index c2d3e6b39d..7cd93bd7ed 100644 --- a/server/src/immich/api-v1/asset/asset.controller.ts +++ b/server/src/immich/api-v1/asset/asset.controller.ts @@ -19,7 +19,7 @@ import { ValidationPipe, } from '@nestjs/common'; import { FileFieldsInterceptor } from '@nestjs/platform-express'; -import { ApiBody, ApiConsumes, ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger'; import { Response as Res } from 'express'; import { Authenticated, AuthUser, SharedLinkRoute } from '../../app.guard'; import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config'; @@ -122,7 +122,6 @@ export class AssetController { @SharedLinkRoute() @Get('/file/:id') @Header('Cache-Control', 'private, max-age=86400, no-transform') - @ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } }) serveFile( @AuthUser() authUser: AuthUserDto, @Headers() headers: Record, @@ -136,7 +135,6 @@ export class AssetController { @SharedLinkRoute() @Get('/thumbnail/:id') @Header('Cache-Control', 'private, max-age=86400, no-transform') - @ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } }) getAssetThumbnail( @AuthUser() authUser: AuthUserDto, @Headers() headers: Record, diff --git a/server/src/immich/api-v1/asset/asset.service.ts b/server/src/immich/api-v1/asset/asset.service.ts index 0a1ee8e0e2..26c0ca7bbe 100644 --- a/server/src/immich/api-v1/asset/asset.service.ts +++ b/server/src/immich/api-v1/asset/asset.service.ts @@ -256,8 +256,8 @@ export class AssetService { } try { - const thumbnailPath = this.getThumbnailPath(asset, query.format); - return this.streamFile(thumbnailPath, res, headers); + const [thumbnailPath, contentType] = this.getThumbnailPath(asset, query.format); + return this.streamFile(thumbnailPath, res, headers, contentType); } catch (e) { res.header('Cache-Control', 'none'); this.logger.error(`Cannot create read stream for asset ${asset.id}`, 'getAssetThumbnail'); @@ -522,16 +522,17 @@ export class AssetService { private getThumbnailPath(asset: AssetEntity, format: GetAssetThumbnailFormatEnum) { switch (format) { case GetAssetThumbnailFormatEnum.WEBP: - if (asset.webpPath && asset.webpPath.length > 0) { - return asset.webpPath; + if (asset.webpPath) { + return [asset.webpPath, 'image/webp']; } + this.logger.warn(`WebP thumbnail requested but not found for asset ${asset.id}, falling back to JPEG`); case GetAssetThumbnailFormatEnum.JPEG: default: if (!asset.resizePath) { - throw new NotFoundException('resizePath not set'); + throw new NotFoundException(`No thumbnail found for asset ${asset.id}`); } - return asset.resizePath; + return [asset.resizePath, 'image/jpeg']; } } diff --git a/server/src/immich/api-v1/asset/dto/get-asset-thumbnail.dto.ts b/server/src/immich/api-v1/asset/dto/get-asset-thumbnail.dto.ts index 5a8dc06872..ad0e755d6a 100644 --- a/server/src/immich/api-v1/asset/dto/get-asset-thumbnail.dto.ts +++ b/server/src/immich/api-v1/asset/dto/get-asset-thumbnail.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional } from 'class-validator'; +import { IsEnum, IsOptional } from 'class-validator'; export enum GetAssetThumbnailFormatEnum { JPEG = 'JPEG', @@ -8,6 +8,7 @@ export enum GetAssetThumbnailFormatEnum { export class GetAssetThumbnailDto { @IsOptional() + @IsEnum(GetAssetThumbnailFormatEnum) @ApiProperty({ type: String, enum: GetAssetThumbnailFormatEnum, diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index 5752304598..2e8135161f 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -7,7 +7,7 @@ import { PersonUpdateDto, } from '@app/domain'; import { Body, Controller, Get, Param, Put, StreamableFile } from '@nestjs/common'; -import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { ApiTags } from '@nestjs/swagger'; import { Authenticated, AuthUser } from '../app.guard'; import { UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; @@ -43,7 +43,6 @@ export class PersonController { } @Get(':id/thumbnail') - @ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } }) getPersonThumbnail(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) { return this.service.getThumbnail(authUser, id).then(asStreamableFile); }