Prechádzať zdrojové kódy

Create database and model for EXIF

Alex Tran 3 rokov pred
rodič
commit
ea9766a60b

+ 5 - 2
server/src/api-v1/asset/asset.controller.ts

@@ -30,6 +30,7 @@ import { promisify } from 'util';
 import { stat } from 'fs';
 import { stat } from 'fs';
 import { pipeline } from 'stream';
 import { pipeline } from 'stream';
 import { GetNewAssetQueryDto } from './dto/get-new-asset-query.dto';
 import { GetNewAssetQueryDto } from './dto/get-new-asset-query.dto';
+import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
 
 
 const fileInfo = promisify(stat);
 const fileInfo = promisify(stat);
 
 
@@ -37,8 +38,9 @@ const fileInfo = promisify(stat);
 @Controller('asset')
 @Controller('asset')
 export class AssetController {
 export class AssetController {
   constructor(
   constructor(
-    private readonly assetService: AssetService,
-    private readonly assetOptimizeService: AssetOptimizeService,
+    private assetService: AssetService,
+    private assetOptimizeService: AssetOptimizeService,
+    private backgroundTaskService: BackgroundTaskService,
   ) {}
   ) {}
 
 
   @Post('upload')
   @Post('upload')
@@ -53,6 +55,7 @@ export class AssetController {
 
 
       if (savedAsset && savedAsset.type == AssetType.IMAGE) {
       if (savedAsset && savedAsset.type == AssetType.IMAGE) {
         await this.assetOptimizeService.resizeImage(savedAsset);
         await this.assetOptimizeService.resizeImage(savedAsset);
+        await this.backgroundTaskService.extractExif(savedAsset);
       }
       }
 
 
       if (savedAsset && savedAsset.type == AssetType.VIDEO) {
       if (savedAsset && savedAsset.type == AssetType.VIDEO) {

+ 12 - 1
server/src/api-v1/asset/asset.module.ts

@@ -6,6 +6,8 @@ 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 { AssetOptimizeService } 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';
+import { BackgroundTaskModule } from '../../modules/background-task/background-task.module';
+import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
 
 
 @Module({
 @Module({
   imports: [
   imports: [
@@ -17,11 +19,20 @@ import { BullModule } from '@nestjs/bull';
         removeOnFail: false,
         removeOnFail: false,
       },
       },
     }),
     }),
+    BullModule.registerQueue({
+      name: 'background-task',
+      defaultJobOptions: {
+        attempts: 3,
+        removeOnComplete: true,
+        removeOnFail: false,
+      },
+    }),
     TypeOrmModule.forFeature([AssetEntity]),
     TypeOrmModule.forFeature([AssetEntity]),
     ImageOptimizeModule,
     ImageOptimizeModule,
+    BackgroundTaskModule,
   ],
   ],
   controllers: [AssetController],
   controllers: [AssetController],
-  providers: [AssetService, AssetOptimizeService],
+  providers: [AssetService, AssetOptimizeService, BackgroundTaskService],
   exports: [],
   exports: [],
 })
 })
 export class AssetModule {}
 export class AssetModule {}

+ 48 - 0
server/src/api-v1/asset/dto/create-exif.dto.ts

@@ -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 - 0
server/src/api-v1/asset/dto/update-exif.dto.ts

@@ -0,0 +1,4 @@
+import { PartialType } from '@nestjs/mapped-types';
+import { CreateExifDto } from './create-exif.dto';
+
+export class UpdateExifDto extends PartialType(CreateExifDto) {}

+ 6 - 1
server/src/api-v1/asset/entities/asset.entity.ts

@@ -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')
 @Entity('assets')
 @Unique(['deviceAssetId', 'userId', 'deviceId'])
 @Unique(['deviceAssetId', 'userId', 'deviceId'])
@@ -38,6 +39,10 @@ export class AssetEntity {
 
 
   @Column({ nullable: true })
   @Column({ nullable: true })
   duration: string;
   duration: string;
+
+  @OneToOne(() => ExifEntity, { cascade: true })
+  @JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
+  exifData?: ExifEntity;
 }
 }
 
 
 export enum AssetType {
 export enum AssetType {

+ 56 - 0
server/src/api-v1/asset/entities/exif.entity.ts

@@ -0,0 +1,56 @@
+import { Index } from 'typeorm';
+import { Column } from 'typeorm/decorator/columns/Column';
+import { PrimaryGeneratedColumn } from 'typeorm/decorator/columns/PrimaryGeneratedColumn';
+import { Entity } from 'typeorm/decorator/entity/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 })
+  modifiedDate: Date;
+
+  @Column({ nullable: true })
+  lensModel: string;
+
+  @Column({ nullable: true })
+  fNumber: number;
+
+  @Column({ nullable: true })
+  focalLenght: number;
+
+  @Column({ nullable: true })
+  iso: number;
+
+  @Column({ nullable: true })
+  exposureTime: number;
+}

+ 3 - 1
server/src/modules/background-task/background-task.module.ts

@@ -2,6 +2,7 @@ import { BullModule } from '@nestjs/bull';
 import { Module } from '@nestjs/common';
 import { Module } from '@nestjs/common';
 import { TypeOrmModule } from '@nestjs/typeorm';
 import { TypeOrmModule } from '@nestjs/typeorm';
 import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
 import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
+import { BackgroundTaskProcessor } from './background-task.processor';
 import { BackgroundTaskService } from './background-task.service';
 import { BackgroundTaskService } from './background-task.service';
 
 
 @Module({
 @Module({
@@ -16,6 +17,7 @@ import { BackgroundTaskService } from './background-task.service';
     }),
     }),
     TypeOrmModule.forFeature([AssetEntity]),
     TypeOrmModule.forFeature([AssetEntity]),
   ],
   ],
-  providers: [BackgroundTaskService],
+  providers: [BackgroundTaskService, BackgroundTaskProcessor],
+  exports: [BackgroundTaskService],
 })
 })
 export class BackgroundTaskModule {}
 export class BackgroundTaskModule {}

+ 21 - 1
server/src/modules/background-task/background-task.processor.ts

@@ -4,13 +4,33 @@ import { Job, Queue } from 'bull';
 import { Repository } from 'typeorm';
 import { Repository } from 'typeorm';
 import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
 import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
 import { ConfigService } from '@nestjs/config';
 import { ConfigService } from '@nestjs/config';
+import exifr from 'exifr';
+import { readFile } from 'fs/promises';
+import { Logger } from '@nestjs/common';
 
 
 @Processor('background-task')
 @Processor('background-task')
-export class ImageOptimizeProcessor {
+export class BackgroundTaskProcessor {
   constructor(
   constructor(
     @InjectRepository(AssetEntity)
     @InjectRepository(AssetEntity)
     private assetRepository: Repository<AssetEntity>,
     private assetRepository: Repository<AssetEntity>,
 
 
     private configService: ConfigService,
     private configService: ConfigService,
   ) {}
   ) {}
+
+  @Process('extract-exif')
+  async extractExif(job: Job) {
+    const { savedAsset }: { savedAsset: AssetEntity } = job.data;
+
+    const fileBuffer = await readFile(savedAsset.originalPath);
+
+    const exifData = await exifr.parse(fileBuffer);
+
+    console.log('=====================');
+    console.log(exifData);
+
+    try {
+    } catch (e) {
+      Logger.error(`Error extracting EXIF ${e.toString()}`, 'extractExif');
+    }
+  }
 }
 }

+ 20 - 1
server/src/modules/background-task/background-task.service.ts

@@ -1,4 +1,23 @@
+import { InjectQueue } from '@nestjs/bull/dist/decorators';
 import { Injectable } from '@nestjs/common';
 import { Injectable } from '@nestjs/common';
+import { Queue } from 'bull';
+import { randomUUID } from 'node:crypto';
+import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
 
 
 @Injectable()
 @Injectable()
-export class BackgroundTaskService {}
+export class BackgroundTaskService {
+  constructor(
+    @InjectQueue('background-task')
+    private backgroundTaskQueue: Queue,
+  ) {}
+
+  async extractExif(savedAsset: AssetEntity) {
+    const job = await this.backgroundTaskQueue.add(
+      'extract-exif',
+      {
+        savedAsset,
+      },
+      { jobId: randomUUID() },
+    );
+  }
+}