mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
Implemented EXIF store and display (#19)
* Added EXIF extracting in the backend * Added EXIF displaying on `image_viewer_page.dart` * Added Icon for backup option not enable
This commit is contained in:
@@ -30,6 +30,7 @@ import { promisify } from 'util';
|
||||
import { stat } from 'fs';
|
||||
import { pipeline } from 'stream';
|
||||
import { GetNewAssetQueryDto } from './dto/get-new-asset-query.dto';
|
||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||
|
||||
const fileInfo = promisify(stat);
|
||||
|
||||
@@ -37,8 +38,9 @@ const fileInfo = promisify(stat);
|
||||
@Controller('asset')
|
||||
export class AssetController {
|
||||
constructor(
|
||||
private readonly assetService: AssetService,
|
||||
private readonly assetOptimizeService: AssetOptimizeService,
|
||||
private assetService: AssetService,
|
||||
private assetOptimizeService: AssetOptimizeService,
|
||||
private backgroundTaskService: BackgroundTaskService,
|
||||
) {}
|
||||
|
||||
@Post('upload')
|
||||
@@ -53,6 +55,7 @@ export class AssetController {
|
||||
|
||||
if (savedAsset && savedAsset.type == AssetType.IMAGE) {
|
||||
await this.assetOptimizeService.resizeImage(savedAsset);
|
||||
await this.backgroundTaskService.extractExif(savedAsset, file.originalname, file.size);
|
||||
}
|
||||
|
||||
if (savedAsset && savedAsset.type == AssetType.VIDEO) {
|
||||
@@ -155,4 +158,9 @@ export class AssetController {
|
||||
async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param('deviceId') deviceId: string) {
|
||||
return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId);
|
||||
}
|
||||
|
||||
@Get('/assetById/:assetId')
|
||||
async getAssetById(@GetAuthUser() authUser: AuthUserDto, @Param('assetId') assetId) {
|
||||
return this.assetService.getAssetById(authUser, assetId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { AssetEntity } from './entities/asset.entity';
|
||||
import { ImageOptimizeModule } from '../../modules/image-optimize/image-optimize.module';
|
||||
import { AssetOptimizeService } from '../../modules/image-optimize/image-optimize.service';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { BackgroundTaskModule } from '../../modules/background-task/background-task.module';
|
||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -17,11 +19,20 @@ import { BullModule } from '@nestjs/bull';
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
BullModule.registerQueue({
|
||||
name: 'background-task',
|
||||
defaultJobOptions: {
|
||||
attempts: 3,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
TypeOrmModule.forFeature([AssetEntity]),
|
||||
ImageOptimizeModule,
|
||||
BackgroundTaskModule,
|
||||
],
|
||||
controllers: [AssetController],
|
||||
providers: [AssetService, AssetOptimizeService],
|
||||
providers: [AssetService, AssetOptimizeService, BackgroundTaskService],
|
||||
exports: [],
|
||||
})
|
||||
export class AssetModule {}
|
||||
|
||||
@@ -112,4 +112,14 @@ export class AssetService {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async getAssetById(authUser: AuthUserDto, assetId: string) {
|
||||
return await this.assetRepository.findOne({
|
||||
where: {
|
||||
userId: authUser.id,
|
||||
id: assetId,
|
||||
},
|
||||
relations: ['exifInfo'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
48
server/src/api-v1/asset/dto/create-exif.dto.ts
Normal file
48
server/src/api-v1/asset/dto/create-exif.dto.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { IsNotEmpty, IsOptional } from 'class-validator';
|
||||
|
||||
export class CreateExifDto {
|
||||
@IsNotEmpty()
|
||||
assetId: string;
|
||||
|
||||
@IsOptional()
|
||||
make: string;
|
||||
|
||||
@IsOptional()
|
||||
model: string;
|
||||
|
||||
@IsOptional()
|
||||
imageName: string;
|
||||
|
||||
@IsOptional()
|
||||
exifImageWidth: number;
|
||||
|
||||
@IsOptional()
|
||||
exifImageHeight: number;
|
||||
|
||||
@IsOptional()
|
||||
fileSizeInByte: number;
|
||||
|
||||
@IsOptional()
|
||||
orientation: string;
|
||||
|
||||
@IsOptional()
|
||||
dateTimeOriginal: Date;
|
||||
|
||||
@IsOptional()
|
||||
modifiedDate: Date;
|
||||
|
||||
@IsOptional()
|
||||
lensModel: string;
|
||||
|
||||
@IsOptional()
|
||||
fNumber: number;
|
||||
|
||||
@IsOptional()
|
||||
focalLenght: number;
|
||||
|
||||
@IsOptional()
|
||||
iso: number;
|
||||
|
||||
@IsOptional()
|
||||
exposureTime: number;
|
||||
}
|
||||
4
server/src/api-v1/asset/dto/update-exif.dto.ts
Normal file
4
server/src/api-v1/asset/dto/update-exif.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateExifDto } from './create-exif.dto';
|
||||
|
||||
export class UpdateExifDto extends PartialType(CreateExifDto) {}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { ExifEntity } from './exif.entity';
|
||||
|
||||
@Entity('assets')
|
||||
@Unique(['deviceAssetId', 'userId', 'deviceId'])
|
||||
@@ -38,6 +39,9 @@ export class AssetEntity {
|
||||
|
||||
@Column({ nullable: true })
|
||||
duration: string;
|
||||
|
||||
@OneToOne(() => ExifEntity, (exifEntity) => exifEntity.asset)
|
||||
exifInfo: ExifEntity;
|
||||
}
|
||||
|
||||
export enum AssetType {
|
||||
|
||||
67
server/src/api-v1/asset/entities/exif.entity.ts
Normal file
67
server/src/api-v1/asset/entities/exif.entity.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Index, JoinColumn, OneToOne } from 'typeorm';
|
||||
import { Column } from 'typeorm/decorator/columns/Column';
|
||||
import { PrimaryGeneratedColumn } from 'typeorm/decorator/columns/PrimaryGeneratedColumn';
|
||||
import { Entity } from 'typeorm/decorator/entity/Entity';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
|
||||
@Entity('exif')
|
||||
export class ExifEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column({ type: 'uuid' })
|
||||
assetId: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
make: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
model: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
imageName: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
exifImageWidth: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
exifImageHeight: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
fileSizeInByte: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
orientation: string;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
dateTimeOriginal: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
modifyDate: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
lensModel: string;
|
||||
|
||||
@Column({ type: 'float8', nullable: true })
|
||||
fNumber: number;
|
||||
|
||||
@Column({ type: 'float8', nullable: true })
|
||||
focalLength: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
iso: number;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
exposureTime: number;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
latitude: number;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
longitude: number;
|
||||
|
||||
@OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true })
|
||||
@JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
|
||||
asset: ExifEntity;
|
||||
}
|
||||
Reference in New Issue
Block a user