1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

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
This commit is contained in:
Mert 2023-07-06 11:27:21 -04:00 committed by GitHub
parent 6f4449d5e9
commit 3cc77d945b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 19 additions and 14 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1673,7 +1673,13 @@
"responses": { "responses": {
"200": { "200": {
"content": { "content": {
"application/octet-stream": { "image/jpeg": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/webp": {
"schema": { "schema": {
"type": "string", "type": "string",
"format": "binary" "format": "binary"
@ -2704,7 +2710,7 @@
"responses": { "responses": {
"200": { "200": {
"content": { "content": {
"application/octet-stream": { "image/jpeg": {
"schema": { "schema": {
"type": "string", "type": "string",
"format": "binary" "format": "binary"

View File

@ -19,7 +19,7 @@ import {
ValidationPipe, ValidationPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { FileFieldsInterceptor } from '@nestjs/platform-express'; 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 { Response as Res } from 'express';
import { Authenticated, AuthUser, SharedLinkRoute } from '../../app.guard'; import { Authenticated, AuthUser, SharedLinkRoute } from '../../app.guard';
import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config'; import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config';
@ -122,7 +122,6 @@ export class AssetController {
@SharedLinkRoute() @SharedLinkRoute()
@Get('/file/:id') @Get('/file/:id')
@Header('Cache-Control', 'private, max-age=86400, no-transform') @Header('Cache-Control', 'private, max-age=86400, no-transform')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
serveFile( serveFile(
@AuthUser() authUser: AuthUserDto, @AuthUser() authUser: AuthUserDto,
@Headers() headers: Record<string, string>, @Headers() headers: Record<string, string>,
@ -136,7 +135,6 @@ export class AssetController {
@SharedLinkRoute() @SharedLinkRoute()
@Get('/thumbnail/:id') @Get('/thumbnail/:id')
@Header('Cache-Control', 'private, max-age=86400, no-transform') @Header('Cache-Control', 'private, max-age=86400, no-transform')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
getAssetThumbnail( getAssetThumbnail(
@AuthUser() authUser: AuthUserDto, @AuthUser() authUser: AuthUserDto,
@Headers() headers: Record<string, string>, @Headers() headers: Record<string, string>,

View File

@ -256,8 +256,8 @@ export class AssetService {
} }
try { try {
const thumbnailPath = this.getThumbnailPath(asset, query.format); const [thumbnailPath, contentType] = this.getThumbnailPath(asset, query.format);
return this.streamFile(thumbnailPath, res, headers); return this.streamFile(thumbnailPath, res, headers, contentType);
} catch (e) { } catch (e) {
res.header('Cache-Control', 'none'); res.header('Cache-Control', 'none');
this.logger.error(`Cannot create read stream for asset ${asset.id}`, 'getAssetThumbnail'); 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) { private getThumbnailPath(asset: AssetEntity, format: GetAssetThumbnailFormatEnum) {
switch (format) { switch (format) {
case GetAssetThumbnailFormatEnum.WEBP: case GetAssetThumbnailFormatEnum.WEBP:
if (asset.webpPath && asset.webpPath.length > 0) { if (asset.webpPath) {
return 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: case GetAssetThumbnailFormatEnum.JPEG:
default: default:
if (!asset.resizePath) { 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'];
} }
} }

View File

@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsOptional } from 'class-validator'; import { IsEnum, IsOptional } from 'class-validator';
export enum GetAssetThumbnailFormatEnum { export enum GetAssetThumbnailFormatEnum {
JPEG = 'JPEG', JPEG = 'JPEG',
@ -8,6 +8,7 @@ export enum GetAssetThumbnailFormatEnum {
export class GetAssetThumbnailDto { export class GetAssetThumbnailDto {
@IsOptional() @IsOptional()
@IsEnum(GetAssetThumbnailFormatEnum)
@ApiProperty({ @ApiProperty({
type: String, type: String,
enum: GetAssetThumbnailFormatEnum, enum: GetAssetThumbnailFormatEnum,

View File

@ -7,7 +7,7 @@ import {
PersonUpdateDto, PersonUpdateDto,
} from '@app/domain'; } from '@app/domain';
import { Body, Controller, Get, Param, Put, StreamableFile } from '@nestjs/common'; 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 { Authenticated, AuthUser } from '../app.guard';
import { UseValidation } from '../app.utils'; import { UseValidation } from '../app.utils';
import { UUIDParamDto } from './dto/uuid-param.dto'; import { UUIDParamDto } from './dto/uuid-param.dto';
@ -43,7 +43,6 @@ export class PersonController {
} }
@Get(':id/thumbnail') @Get(':id/thumbnail')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
getPersonThumbnail(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) { getPersonThumbnail(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) {
return this.service.getThumbnail(authUser, id).then(asStreamableFile); return this.service.getThumbnail(authUser, id).then(asStreamableFile);
} }