1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-26 10:50:29 +02:00

feat(server): enhanced thumbnails generation code (#2147)

* Add size parameter to extractVideoThumbnail

* Ensure minimum dimension of webp thumbnail
This commit is contained in:
Sergey Kondrikov 2023-04-04 04:18:27 +03:00 committed by GitHub
parent fc585bffcc
commit 7e526f87b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 19 additions and 6 deletions

View File

@ -7,6 +7,6 @@ export interface ResizeOptions {
export interface IMediaRepository { export interface IMediaRepository {
resize(input: string, output: string, options: ResizeOptions): Promise<void>; resize(input: string, output: string, options: ResizeOptions): Promise<void>;
extractVideoThumbnail(input: string, output: string): Promise<void>; extractVideoThumbnail(input: string, output: string, size: number): Promise<void>;
extractThumbnailFromExif(input: string, output: string): Promise<void>; extractThumbnailFromExif(input: string, output: string): Promise<void>;
} }

View File

@ -114,6 +114,7 @@ describe(MediaService.name, () => {
expect(mediaMock.extractVideoThumbnail).toHaveBeenCalledWith( expect(mediaMock.extractVideoThumbnail).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/thumbs/user-id/asset-id.jpeg', 'upload/thumbs/user-id/asset-id.jpeg',
1440,
); );
expect(assetMock.save).toHaveBeenCalledWith({ expect(assetMock.save).toHaveBeenCalledWith({
id: 'asset-id', id: 'asset-id',

View File

@ -44,9 +44,13 @@ export class MediaService {
this.storageRepository.mkdirSync(resizePath); this.storageRepository.mkdirSync(resizePath);
const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`); const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`);
const thumbnailDimension = 1440;
if (asset.type == AssetType.IMAGE) { if (asset.type == AssetType.IMAGE) {
try { try {
await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, { size: 1440, format: 'jpeg' }); await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, {
size: thumbnailDimension,
format: 'jpeg',
});
} catch (error) { } catch (error) {
this.logger.warn( this.logger.warn(
`Failed to generate jpeg thumbnail using sharp, trying with exiftool-vendored (asset=${asset.id})`, `Failed to generate jpeg thumbnail using sharp, trying with exiftool-vendored (asset=${asset.id})`,
@ -57,7 +61,7 @@ export class MediaService {
if (asset.type == AssetType.VIDEO) { if (asset.type == AssetType.VIDEO) {
this.logger.log('Start Generating Video Thumbnail'); this.logger.log('Start Generating Video Thumbnail');
await this.mediaRepository.extractVideoThumbnail(asset.originalPath, jpegThumbnailPath); await this.mediaRepository.extractVideoThumbnail(asset.originalPath, jpegThumbnailPath, thumbnailDimension);
this.logger.log(`Generating Video Thumbnail Success ${asset.id}`); this.logger.log(`Generating Video Thumbnail Success ${asset.id}`);
} }

View File

@ -11,7 +11,11 @@ export class MediaRepository implements IMediaRepository {
async resize(input: string, output: string, options: ResizeOptions): Promise<void> { async resize(input: string, output: string, options: ResizeOptions): Promise<void> {
switch (options.format) { switch (options.format) {
case 'webp': case 'webp':
await sharp(input, { failOnError: false }).resize(250).webp().rotate().toFile(output); await sharp(input, { failOnError: false })
.resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true })
.webp()
.rotate()
.toFile(output);
return; return;
case 'jpeg': case 'jpeg':
@ -24,10 +28,14 @@ export class MediaRepository implements IMediaRepository {
} }
} }
extractVideoThumbnail(input: string, output: string) { extractVideoThumbnail(input: string, output: string, size: number) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
ffmpeg(input) ffmpeg(input)
.outputOptions(['-ss 00:00:00.000', '-frames:v 1']) .outputOptions([
'-ss 00:00:00.000',
'-frames:v 1',
`-vf scale='min(${size},iw)':'min(${size},ih)':force_original_aspect_ratio=increase`,
])
.output(output) .output(output)
.on('error', reject) .on('error', reject)
.on('end', resolve) .on('end', resolve)