From 44fc358b5d0acd3269bac79861fa8915052deb9f Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Thu, 10 Feb 2022 00:11:48 -0600 Subject: [PATCH] Extract and store EXIF on uploaded, create endpoint to get asset with exif data --- server/src/api-v1/asset/asset.controller.ts | 7 ++++- server/src/api-v1/asset/asset.service.ts | 10 +++++++ .../src/api-v1/asset/entities/asset.entity.ts | 5 ++-- .../src/api-v1/asset/entities/exif.entity.ts | 23 +++++++++++---- .../background-task/background-task.module.ts | 3 +- .../background-task.processor.ts | 29 +++++++++++++++++-- .../background-task.service.ts | 4 ++- .../image-optimize/image-optimize.service.ts | 2 +- 8 files changed, 67 insertions(+), 16 deletions(-) diff --git a/server/src/api-v1/asset/asset.controller.ts b/server/src/api-v1/asset/asset.controller.ts index 36ab115105..81b499d299 100644 --- a/server/src/api-v1/asset/asset.controller.ts +++ b/server/src/api-v1/asset/asset.controller.ts @@ -55,7 +55,7 @@ export class AssetController { if (savedAsset && savedAsset.type == AssetType.IMAGE) { await this.assetOptimizeService.resizeImage(savedAsset); - await this.backgroundTaskService.extractExif(savedAsset); + await this.backgroundTaskService.extractExif(savedAsset, file.originalname, file.size); } if (savedAsset && savedAsset.type == AssetType.VIDEO) { @@ -158,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); + } } diff --git a/server/src/api-v1/asset/asset.service.ts b/server/src/api-v1/asset/asset.service.ts index 4d5c672f4d..2ffebbff51 100644 --- a/server/src/api-v1/asset/asset.service.ts +++ b/server/src/api-v1/asset/asset.service.ts @@ -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'], + }); + } } diff --git a/server/src/api-v1/asset/entities/asset.entity.ts b/server/src/api-v1/asset/entities/asset.entity.ts index 640783ed99..8fd1be779d 100644 --- a/server/src/api-v1/asset/entities/asset.entity.ts +++ b/server/src/api-v1/asset/entities/asset.entity.ts @@ -40,9 +40,8 @@ export class AssetEntity { @Column({ nullable: true }) duration: string; - @OneToOne(() => ExifEntity, { cascade: true }) - @JoinColumn({ name: 'assetId', referencedColumnName: 'id' }) - exifData?: ExifEntity; + @OneToOne(() => ExifEntity, (exifEntity) => exifEntity.asset) + exifInfo: ExifEntity; } export enum AssetType { diff --git a/server/src/api-v1/asset/entities/exif.entity.ts b/server/src/api-v1/asset/entities/exif.entity.ts index ac4ec9d7e5..ffc4246410 100644 --- a/server/src/api-v1/asset/entities/exif.entity.ts +++ b/server/src/api-v1/asset/entities/exif.entity.ts @@ -1,7 +1,8 @@ -import { Index } from 'typeorm'; +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 { @@ -37,20 +38,30 @@ export class ExifEntity { dateTimeOriginal: Date; @Column({ type: 'timestamptz', nullable: true }) - modifiedDate: Date; + modifyDate: Date; @Column({ nullable: true }) lensModel: string; - @Column({ nullable: true }) + @Column({ type: 'float8', nullable: true }) fNumber: number; - @Column({ nullable: true }) - focalLenght: number; + @Column({ type: 'float8', nullable: true }) + focalLength: number; @Column({ nullable: true }) iso: number; - @Column({ nullable: true }) + @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; } diff --git a/server/src/modules/background-task/background-task.module.ts b/server/src/modules/background-task/background-task.module.ts index 9c5d8645fb..3bd2e419a9 100644 --- a/server/src/modules/background-task/background-task.module.ts +++ b/server/src/modules/background-task/background-task.module.ts @@ -2,6 +2,7 @@ import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AssetEntity } from '../../api-v1/asset/entities/asset.entity'; +import { ExifEntity } from '../../api-v1/asset/entities/exif.entity'; import { BackgroundTaskProcessor } from './background-task.processor'; import { BackgroundTaskService } from './background-task.service'; @@ -15,7 +16,7 @@ import { BackgroundTaskService } from './background-task.service'; removeOnFail: false, }, }), - TypeOrmModule.forFeature([AssetEntity]), + TypeOrmModule.forFeature([AssetEntity, ExifEntity]), ], providers: [BackgroundTaskService, BackgroundTaskProcessor], exports: [BackgroundTaskService], diff --git a/server/src/modules/background-task/background-task.processor.ts b/server/src/modules/background-task/background-task.processor.ts index f2b9d33ef1..4424756654 100644 --- a/server/src/modules/background-task/background-task.processor.ts +++ b/server/src/modules/background-task/background-task.processor.ts @@ -7,6 +7,7 @@ import { ConfigService } from '@nestjs/config'; import exifr from 'exifr'; import { readFile } from 'fs/promises'; import { Logger } from '@nestjs/common'; +import { ExifEntity } from '../../api-v1/asset/entities/exif.entity'; @Processor('background-task') export class BackgroundTaskProcessor { @@ -14,19 +15,41 @@ export class BackgroundTaskProcessor { @InjectRepository(AssetEntity) private assetRepository: Repository, + @InjectRepository(ExifEntity) + private exifRepository: Repository, + private configService: ConfigService, ) {} @Process('extract-exif') async extractExif(job: Job) { - const { savedAsset }: { savedAsset: AssetEntity } = job.data; + const { savedAsset, fileName, fileSize }: { savedAsset: AssetEntity; fileName: string; fileSize: number } = + job.data; const fileBuffer = await readFile(savedAsset.originalPath); const exifData = await exifr.parse(fileBuffer); - console.log('====================='); - console.log(exifData); + const newExif = new ExifEntity(); + newExif.assetId = savedAsset.id; + newExif.make = exifData['Make'] || null; + newExif.model = exifData['Model'] || null; + newExif.imageName = fileName || null; + newExif.exifImageHeight = exifData['ExifImageHeight'] || null; + newExif.exifImageWidth = exifData['ExifImageWidth'] || null; + newExif.fileSizeInByte = fileSize || null; + newExif.orientation = exifData['Orientation'] || null; + newExif.dateTimeOriginal = exifData['DateTimeOriginal'] || null; + newExif.modifyDate = exifData['ModifyDate'] || null; + newExif.lensModel = exifData['LensModel'] || null; + newExif.fNumber = exifData['FNumber'] || null; + newExif.focalLength = exifData['FocalLength'] || null; + newExif.iso = exifData['ISO'] || null; + newExif.exposureTime = exifData['ExposureTime'] || null; + newExif.latitude = exifData['latitude'] || null; + newExif.longitude = exifData['longitude'] || null; + + await this.exifRepository.save(newExif); try { } catch (e) { diff --git a/server/src/modules/background-task/background-task.service.ts b/server/src/modules/background-task/background-task.service.ts index 507840ea30..f0db4d1a92 100644 --- a/server/src/modules/background-task/background-task.service.ts +++ b/server/src/modules/background-task/background-task.service.ts @@ -11,11 +11,13 @@ export class BackgroundTaskService { private backgroundTaskQueue: Queue, ) {} - async extractExif(savedAsset: AssetEntity) { + async extractExif(savedAsset: AssetEntity, fileName: string, fileSize: number) { const job = await this.backgroundTaskQueue.add( 'extract-exif', { savedAsset, + fileName, + fileSize, }, { jobId: randomUUID() }, ); diff --git a/server/src/modules/image-optimize/image-optimize.service.ts b/server/src/modules/image-optimize/image-optimize.service.ts index 37f7a488be..31c0a466c0 100644 --- a/server/src/modules/image-optimize/image-optimize.service.ts +++ b/server/src/modules/image-optimize/image-optimize.service.ts @@ -22,7 +22,7 @@ export class AssetOptimizeService { }; } - public async getVideoThumbnail(savedAsset: AssetEntity, filename: String) { + public async getVideoThumbnail(savedAsset: AssetEntity, filename: string) { const job = await this.optimizeQueue.add( 'get-video-thumbnail', {