mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
Add webp thumbnail conversion task to optimize performance of fast scrolling (#172)
* Update readme * Added webp to table and entity * Added cronjob and sharp dependencies * Added conversion of webp every 5 minutes and endpoint will now server webp image if exist
This commit is contained in:
@@ -114,7 +114,11 @@ export class AssetService {
|
||||
public async getAssetThumbnail(assetId: string) {
|
||||
const asset = await this.assetRepository.findOne({ id: assetId });
|
||||
|
||||
return new StreamableFile(createReadStream(asset.resizePath));
|
||||
if (asset.webpPath != '') {
|
||||
return new StreamableFile(createReadStream(asset.webpPath));
|
||||
} else {
|
||||
return new StreamableFile(createReadStream(asset.resizePath));
|
||||
}
|
||||
}
|
||||
|
||||
public async serveFile(authUser: AuthUserDto, query: ServeFileDto, res: Res, headers: any) {
|
||||
@@ -132,7 +136,11 @@ export class AssetService {
|
||||
if (query.isThumb === 'false' || !query.isThumb) {
|
||||
file = createReadStream(asset.originalPath);
|
||||
} else {
|
||||
file = createReadStream(asset.resizePath);
|
||||
if (asset.webpPath != '') {
|
||||
file = createReadStream(asset.webpPath);
|
||||
} else {
|
||||
file = createReadStream(asset.resizePath);
|
||||
}
|
||||
}
|
||||
|
||||
file.on('error', (error) => {
|
||||
|
||||
@@ -26,6 +26,9 @@ export class AssetEntity {
|
||||
@Column({ nullable: true })
|
||||
resizePath: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
webpPath: string;
|
||||
|
||||
@Column()
|
||||
createdAt: string;
|
||||
|
||||
|
||||
@@ -16,16 +16,26 @@ import { BackgroundTaskModule } from './modules/background-task/background-task.
|
||||
import { CommunicationModule } from './api-v1/communication/communication.module';
|
||||
import { SharingModule } from './api-v1/sharing/sharing.module';
|
||||
import { AppController } from './app.controller';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module';
|
||||
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(immichAppConfig),
|
||||
|
||||
TypeOrmModule.forRoot(databaseConfig),
|
||||
|
||||
UserModule,
|
||||
|
||||
AssetModule,
|
||||
|
||||
AuthModule,
|
||||
|
||||
ImmichJwtModule,
|
||||
|
||||
DeviceInfoModule,
|
||||
|
||||
BullModule.forRootAsync({
|
||||
useFactory: async () => ({
|
||||
redis: {
|
||||
@@ -44,6 +54,10 @@ import { AppController } from './app.controller';
|
||||
CommunicationModule,
|
||||
|
||||
SharingModule,
|
||||
|
||||
ScheduleModule.forRoot(),
|
||||
|
||||
ScheduleTasksModule
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [],
|
||||
|
||||
@@ -17,5 +17,6 @@ export const immichAppConfig: ConfigModuleOptions = {
|
||||
then: Joi.string().optional().allow(null, ''),
|
||||
otherwise: Joi.string().required(),
|
||||
}),
|
||||
VITE_SERVER_ENDPOINT: Joi.string().required(),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
export const serverVersion = {
|
||||
major: 1,
|
||||
minor: 9,
|
||||
minor: 10,
|
||||
patch: 0,
|
||||
build: 13,
|
||||
build: 14,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class UpdateAssetTableWithWebpPath1653214255670 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
alter table assets
|
||||
add column if not exists "webpPath" varchar default '';
|
||||
`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
alter table assets
|
||||
drop column if exists "webpPath";
|
||||
`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
|
||||
import sharp from 'sharp';
|
||||
|
||||
@Injectable()
|
||||
export class ImageConversionService {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>
|
||||
) { }
|
||||
|
||||
@Cron(CronExpression.EVERY_5_MINUTES
|
||||
, {
|
||||
name: 'webp-conversion'
|
||||
})
|
||||
async webpConversion() {
|
||||
Logger.log('Starting Webp Conversion Tasks', 'ImageConversionService')
|
||||
|
||||
const assets = await this.assetRepository.find({
|
||||
where: {
|
||||
webpPath: ''
|
||||
},
|
||||
take: 500
|
||||
});
|
||||
|
||||
|
||||
if (assets.length == 0) {
|
||||
Logger.log('All assets has webp file - aborting task', 'ImageConversionService')
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (const asset of assets) {
|
||||
const resizePath = asset.resizePath;
|
||||
if (resizePath != '') {
|
||||
const webpPath = resizePath.replace('jpeg', 'webp')
|
||||
|
||||
sharp(resizePath).resize(250).webp().toFile(webpPath, (err, info) => {
|
||||
|
||||
if (!err) {
|
||||
this.assetRepository.update({ id: asset.id }, { webpPath: webpPath })
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
server/src/modules/schedule-tasks/schedule-tasks.module.ts
Normal file
12
server/src/modules/schedule-tasks/schedule-tasks.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
|
||||
import { ImageConversionService } from './image-conversion.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AssetEntity]),
|
||||
],
|
||||
providers: [ImageConversionService],
|
||||
})
|
||||
export class ScheduleTasksModule { }
|
||||
Reference in New Issue
Block a user