mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
setup image resize processor
This commit is contained in:
parent
0020c7be94
commit
168676075f
8 changed files with 80 additions and 21 deletions
2
Makefile
2
Makefile
|
|
@ -1,5 +1,5 @@
|
||||||
dev:
|
dev:
|
||||||
docker-compose -f ./server/docker-compose.yml up
|
docker-compose -f ./server/docker-compose.yml up
|
||||||
|
|
||||||
update:
|
dev-update:
|
||||||
docker-compose -f ./server/docker-compose.yml up --build -V
|
docker-compose -f ./server/docker-compose.yml up --build -V
|
||||||
|
|
@ -35,7 +35,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||||
void getBackupInfo() async {
|
void getBackupInfo() async {
|
||||||
_updateServerInfo();
|
_updateServerInfo();
|
||||||
|
|
||||||
List<AssetPathEntity> list = await PhotoManager.getAssetPathList(onlyAll: true, type: RequestType.image);
|
List<AssetPathEntity> list = await PhotoManager.getAssetPathList(onlyAll: true, type: RequestType.common);
|
||||||
|
|
||||||
if (list.isEmpty) {
|
if (list.isEmpty) {
|
||||||
debugPrint("No Asset On Device");
|
debugPrint("No Asset On Device");
|
||||||
|
|
@ -59,7 +59,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||||
// await PhotoManager.presentLimited();
|
// await PhotoManager.presentLimited();
|
||||||
// Gather assets info
|
// Gather assets info
|
||||||
List<AssetPathEntity> list =
|
List<AssetPathEntity> list =
|
||||||
await PhotoManager.getAssetPathList(hasAll: true, onlyAll: true, type: RequestType.image);
|
await PhotoManager.getAssetPathList(hasAll: true, onlyAll: true, type: RequestType.common);
|
||||||
|
|
||||||
if (list.isEmpty) {
|
if (list.isEmpty) {
|
||||||
debugPrint("No Asset On Device - Abort Backup Process");
|
debugPrint("No Asset On Device - Abort Backup Process");
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import 'package:immich_mobile/utils/files_helper.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:exif/exif.dart';
|
|
||||||
|
|
||||||
class BackupService {
|
class BackupService {
|
||||||
final NetworkService _networkService = NetworkService();
|
final NetworkService _networkService = NetworkService();
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
import { createReadStream } from 'fs';
|
import { createReadStream } from 'fs';
|
||||||
import { ServeFileDto } from './dto/serve-file.dto';
|
import { ServeFileDto } from './dto/serve-file.dto';
|
||||||
import { ImageOptimizeService } from '../../modules/image-optimize/image-optimize.service';
|
import { AssetOptimizeService } from '../../modules/image-optimize/image-optimize.service';
|
||||||
import { AssetType } from './entities/asset.entity';
|
import { AssetType } from './entities/asset.entity';
|
||||||
import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
|
import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
|
||||||
export class AssetController {
|
export class AssetController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly assetService: AssetService,
|
private readonly assetService: AssetService,
|
||||||
private readonly imageOptimizeService: ImageOptimizeService,
|
private readonly assetOptimizeService: AssetOptimizeService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('upload')
|
@Post('upload')
|
||||||
|
|
@ -45,7 +45,11 @@ export class AssetController {
|
||||||
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype);
|
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype);
|
||||||
|
|
||||||
if (savedAsset && savedAsset.type == AssetType.IMAGE) {
|
if (savedAsset && savedAsset.type == AssetType.IMAGE) {
|
||||||
await this.imageOptimizeService.resizeImage(savedAsset);
|
await this.assetOptimizeService.resizeImage(savedAsset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedAsset && savedAsset.type == AssetType.IMAGE) {
|
||||||
|
await this.assetOptimizeService.resizeVideo(savedAsset);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import { AssetController } from './asset.controller';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AssetEntity } from './entities/asset.entity';
|
import { AssetEntity } from './entities/asset.entity';
|
||||||
import { ImageOptimizeModule } from '../../modules/image-optimize/image-optimize.module';
|
import { ImageOptimizeModule } from '../../modules/image-optimize/image-optimize.module';
|
||||||
import { ImageOptimizeService } from '../../modules/image-optimize/image-optimize.service';
|
import { AssetOptimizeService } from '../../modules/image-optimize/image-optimize.service';
|
||||||
import { BullModule } from '@nestjs/bull';
|
import { BullModule } from '@nestjs/bull';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
BullModule.registerQueue({
|
BullModule.registerQueue({
|
||||||
name: 'image',
|
name: 'optimize',
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
|
|
@ -29,7 +29,7 @@ import { BullModule } from '@nestjs/bull';
|
||||||
ImageOptimizeModule,
|
ImageOptimizeModule,
|
||||||
],
|
],
|
||||||
controllers: [AssetController],
|
controllers: [AssetController],
|
||||||
providers: [AssetService, ImageOptimizeService],
|
providers: [AssetService, AssetOptimizeService],
|
||||||
exports: [],
|
exports: [],
|
||||||
})
|
})
|
||||||
export class AssetModule {}
|
export class AssetModule {}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ import { AssetModule } from '../../api-v1/asset/asset.module';
|
||||||
import { AssetService } from '../../api-v1/asset/asset.service';
|
import { AssetService } from '../../api-v1/asset/asset.service';
|
||||||
import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
|
import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
|
||||||
import { ImageOptimizeProcessor } from './image-optimize.processor';
|
import { ImageOptimizeProcessor } from './image-optimize.processor';
|
||||||
import { ImageOptimizeService } from './image-optimize.service';
|
import { AssetOptimizeService } from './image-optimize.service';
|
||||||
import { MachineLearningProcessor } from './machine-learning.processor';
|
import { MachineLearningProcessor } from './machine-learning.processor';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
BullModule.registerQueue({
|
BullModule.registerQueue({
|
||||||
name: 'image',
|
name: 'optimize',
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
|
|
@ -30,7 +30,7 @@ import { MachineLearningProcessor } from './machine-learning.processor';
|
||||||
|
|
||||||
TypeOrmModule.forFeature([AssetEntity]),
|
TypeOrmModule.forFeature([AssetEntity]),
|
||||||
],
|
],
|
||||||
providers: [ImageOptimizeService, ImageOptimizeProcessor, MachineLearningProcessor],
|
providers: [AssetOptimizeService, ImageOptimizeProcessor, MachineLearningProcessor],
|
||||||
exports: [ImageOptimizeService],
|
exports: [AssetOptimizeService],
|
||||||
})
|
})
|
||||||
export class ImageOptimizeModule {}
|
export class ImageOptimizeModule {}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import fs, { existsSync, mkdirSync } from 'fs';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
@Processor('image')
|
@Processor('optimize')
|
||||||
export class ImageOptimizeProcessor {
|
export class ImageOptimizeProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||||
|
|
@ -16,8 +16,8 @@ export class ImageOptimizeProcessor {
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Process('optimize')
|
@Process('resize-image')
|
||||||
async handleOptimization(job: Job) {
|
async resizeUploadedImage(job: Job) {
|
||||||
const { savedAsset }: { savedAsset: AssetEntity } = job.data;
|
const { savedAsset }: { savedAsset: AssetEntity } = job.data;
|
||||||
|
|
||||||
const basePath = this.configService.get('UPLOAD_LOCATION');
|
const basePath = this.configService.get('UPLOAD_LOCATION');
|
||||||
|
|
@ -58,4 +58,46 @@ export class ImageOptimizeProcessor {
|
||||||
|
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Process('resize-video')
|
||||||
|
async resizeUploadedVideo(job: Job) {
|
||||||
|
const { savedAsset }: { savedAsset: AssetEntity } = job.data;
|
||||||
|
|
||||||
|
const basePath = this.configService.get('UPLOAD_LOCATION');
|
||||||
|
const resizePath = savedAsset.originalPath.replace('/original/', '/thumb/');
|
||||||
|
|
||||||
|
// Create folder for thumb image if not exist
|
||||||
|
const resizeDir = `${basePath}/${savedAsset.userId}/thumb/${savedAsset.deviceId}`;
|
||||||
|
|
||||||
|
if (!existsSync(resizeDir)) {
|
||||||
|
mkdirSync(resizeDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readFile(savedAsset.originalPath, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error Reading File');
|
||||||
|
}
|
||||||
|
|
||||||
|
sharp(data)
|
||||||
|
.resize(512, 512, { fit: 'outside' })
|
||||||
|
.toFile(resizePath, async (err, info) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error resizing file ', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.assetRepository.update(savedAsset, { resizePath: resizePath });
|
||||||
|
|
||||||
|
// Send file to object detection after resizing
|
||||||
|
// const detectionJob = await this.machineLearningQueue.add(
|
||||||
|
// 'object-detection',
|
||||||
|
// {
|
||||||
|
// resizePath,
|
||||||
|
// },
|
||||||
|
// { jobId: randomUUID() },
|
||||||
|
// );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return 'ok';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,26 @@ import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImageOptimizeService {
|
export class AssetOptimizeService {
|
||||||
constructor(@InjectQueue('image') private imageQueue: Queue) {}
|
constructor(@InjectQueue('optimize') private optimizeQueue: Queue) {}
|
||||||
|
|
||||||
public async resizeImage(savedAsset: AssetEntity) {
|
public async resizeImage(savedAsset: AssetEntity) {
|
||||||
const job = await this.imageQueue.add(
|
const job = await this.optimizeQueue.add(
|
||||||
'optimize',
|
'resize-image',
|
||||||
|
{
|
||||||
|
savedAsset,
|
||||||
|
},
|
||||||
|
{ jobId: randomUUID() },
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
jobId: job.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resizeVideo(savedAsset: AssetEntity) {
|
||||||
|
const job = await this.optimizeQueue.add(
|
||||||
|
'resize-video',
|
||||||
{
|
{
|
||||||
savedAsset,
|
savedAsset,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue