mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-25 16:04:21 -04:00 
			
		
		
		
	feat(server): add originalFileName to asset table (#2231)
This commit is contained in:
		
							parent
							
								
									db628cec11
								
							
						
					
					
						commit
						a1a62b00a0
					
				
							
								
								
									
										1
									
								
								mobile/openapi/doc/AssetResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/AssetResponseDto.md
									
									
									
										generated
									
									
									
								
							| @ -14,6 +14,7 @@ Name | Type | Description | Notes | ||||
| **ownerId** | **String** |  |  | ||||
| **deviceId** | **String** |  |  | ||||
| **originalPath** | **String** |  |  | ||||
| **originalFileName** | **String** |  |  | ||||
| **resizePath** | **String** |  |  | ||||
| **fileCreatedAt** | **String** |  |  | ||||
| **fileModifiedAt** | **String** |  |  | ||||
|  | ||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/ExifResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/ExifResponseDto.md
									
									
									
										generated
									
									
									
								
							| @ -11,7 +11,6 @@ Name | Type | Description | Notes | ||||
| **fileSizeInByte** | **int** |  | [optional]  | ||||
| **make** | **String** |  | [optional]  | ||||
| **model** | **String** |  | [optional]  | ||||
| **imageName** | **String** |  | [optional]  | ||||
| **exifImageWidth** | **num** |  | [optional]  | ||||
| **exifImageHeight** | **num** |  | [optional]  | ||||
| **orientation** | **String** |  | [optional]  | ||||
|  | ||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							| @ -19,6 +19,7 @@ class AssetResponseDto { | ||||
|     required this.ownerId, | ||||
|     required this.deviceId, | ||||
|     required this.originalPath, | ||||
|     required this.originalFileName, | ||||
|     required this.resizePath, | ||||
|     required this.fileCreatedAt, | ||||
|     required this.fileModifiedAt, | ||||
| @ -46,6 +47,8 @@ class AssetResponseDto { | ||||
| 
 | ||||
|   String originalPath; | ||||
| 
 | ||||
|   String originalFileName; | ||||
| 
 | ||||
|   String? resizePath; | ||||
| 
 | ||||
|   String fileCreatedAt; | ||||
| @ -92,6 +95,7 @@ class AssetResponseDto { | ||||
|      other.ownerId == ownerId && | ||||
|      other.deviceId == deviceId && | ||||
|      other.originalPath == originalPath && | ||||
|      other.originalFileName == originalFileName && | ||||
|      other.resizePath == resizePath && | ||||
|      other.fileCreatedAt == fileCreatedAt && | ||||
|      other.fileModifiedAt == fileModifiedAt && | ||||
| @ -115,6 +119,7 @@ class AssetResponseDto { | ||||
|     (ownerId.hashCode) + | ||||
|     (deviceId.hashCode) + | ||||
|     (originalPath.hashCode) + | ||||
|     (originalFileName.hashCode) + | ||||
|     (resizePath == null ? 0 : resizePath!.hashCode) + | ||||
|     (fileCreatedAt.hashCode) + | ||||
|     (fileModifiedAt.hashCode) + | ||||
| @ -130,7 +135,7 @@ class AssetResponseDto { | ||||
|     (tags.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]'; | ||||
|   String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resizePath=$resizePath, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
| @ -140,6 +145,7 @@ class AssetResponseDto { | ||||
|       json[r'ownerId'] = this.ownerId; | ||||
|       json[r'deviceId'] = this.deviceId; | ||||
|       json[r'originalPath'] = this.originalPath; | ||||
|       json[r'originalFileName'] = this.originalFileName; | ||||
|     if (this.resizePath != null) { | ||||
|       json[r'resizePath'] = this.resizePath; | ||||
|     } else { | ||||
| @ -209,6 +215,7 @@ class AssetResponseDto { | ||||
|         ownerId: mapValueOfType<String>(json, r'ownerId')!, | ||||
|         deviceId: mapValueOfType<String>(json, r'deviceId')!, | ||||
|         originalPath: mapValueOfType<String>(json, r'originalPath')!, | ||||
|         originalFileName: mapValueOfType<String>(json, r'originalFileName')!, | ||||
|         resizePath: mapValueOfType<String>(json, r'resizePath'), | ||||
|         fileCreatedAt: mapValueOfType<String>(json, r'fileCreatedAt')!, | ||||
|         fileModifiedAt: mapValueOfType<String>(json, r'fileModifiedAt')!, | ||||
| @ -277,6 +284,7 @@ class AssetResponseDto { | ||||
|     'ownerId', | ||||
|     'deviceId', | ||||
|     'originalPath', | ||||
|     'originalFileName', | ||||
|     'resizePath', | ||||
|     'fileCreatedAt', | ||||
|     'fileModifiedAt', | ||||
|  | ||||
							
								
								
									
										13
									
								
								mobile/openapi/lib/model/exif_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								mobile/openapi/lib/model/exif_response_dto.dart
									
									
									
										generated
									
									
									
								
							| @ -16,7 +16,6 @@ class ExifResponseDto { | ||||
|     this.fileSizeInByte, | ||||
|     this.make, | ||||
|     this.model, | ||||
|     this.imageName, | ||||
|     this.exifImageWidth, | ||||
|     this.exifImageHeight, | ||||
|     this.orientation, | ||||
| @ -41,8 +40,6 @@ class ExifResponseDto { | ||||
| 
 | ||||
|   String? model; | ||||
| 
 | ||||
|   String? imageName; | ||||
| 
 | ||||
|   num? exifImageWidth; | ||||
| 
 | ||||
|   num? exifImageHeight; | ||||
| @ -80,7 +77,6 @@ class ExifResponseDto { | ||||
|      other.fileSizeInByte == fileSizeInByte && | ||||
|      other.make == make && | ||||
|      other.model == model && | ||||
|      other.imageName == imageName && | ||||
|      other.exifImageWidth == exifImageWidth && | ||||
|      other.exifImageHeight == exifImageHeight && | ||||
|      other.orientation == orientation && | ||||
| @ -104,7 +100,6 @@ class ExifResponseDto { | ||||
|     (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + | ||||
|     (make == null ? 0 : make!.hashCode) + | ||||
|     (model == null ? 0 : model!.hashCode) + | ||||
|     (imageName == null ? 0 : imageName!.hashCode) + | ||||
|     (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + | ||||
|     (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + | ||||
|     (orientation == null ? 0 : orientation!.hashCode) + | ||||
| @ -123,7 +118,7 @@ class ExifResponseDto { | ||||
|     (country == null ? 0 : country!.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'ExifResponseDto[fileSizeInByte=$fileSizeInByte, make=$make, model=$model, imageName=$imageName, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, timeZone=$timeZone, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]'; | ||||
|   String toString() => 'ExifResponseDto[fileSizeInByte=$fileSizeInByte, make=$make, model=$model, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, timeZone=$timeZone, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
| @ -142,11 +137,6 @@ class ExifResponseDto { | ||||
|     } else { | ||||
|       // json[r'model'] = null; | ||||
|     } | ||||
|     if (this.imageName != null) { | ||||
|       json[r'imageName'] = this.imageName; | ||||
|     } else { | ||||
|       // json[r'imageName'] = null; | ||||
|     } | ||||
|     if (this.exifImageWidth != null) { | ||||
|       json[r'exifImageWidth'] = this.exifImageWidth; | ||||
|     } else { | ||||
| @ -252,7 +242,6 @@ class ExifResponseDto { | ||||
|         fileSizeInByte: mapValueOfType<int>(json, r'fileSizeInByte'), | ||||
|         make: mapValueOfType<String>(json, r'make'), | ||||
|         model: mapValueOfType<String>(json, r'model'), | ||||
|         imageName: mapValueOfType<String>(json, r'imageName'), | ||||
|         exifImageWidth: json[r'exifImageWidth'] == null | ||||
|             ? null | ||||
|             : num.parse(json[r'exifImageWidth'].toString()), | ||||
|  | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/asset_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/asset_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							| @ -46,6 +46,11 @@ void main() { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // String originalFileName | ||||
|     test('to test the property `originalFileName`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // String resizePath | ||||
|     test('to test the property `resizePath`', () async { | ||||
|       // TODO | ||||
|  | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/exif_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/exif_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							| @ -31,11 +31,6 @@ void main() { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // String imageName | ||||
|     test('to test the property `imageName`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // num exifImageWidth | ||||
|     test('to test the property `exifImageWidth`', () async { | ||||
|       // TODO | ||||
|  | ||||
| @ -2,6 +2,7 @@ import { AuthUserDto, IJobRepository, JobName } from '@app/domain'; | ||||
| import { AssetEntity, UserEntity } from '@app/infra/entities'; | ||||
| import { IAssetRepository } from './asset-repository'; | ||||
| import { CreateAssetDto, UploadFile } from './dto/create-asset.dto'; | ||||
| import { parse } from 'node:path'; | ||||
| 
 | ||||
| export class AssetCore { | ||||
|   constructor(private repository: IAssetRepository, private jobRepository: IJobRepository) {} | ||||
| @ -35,6 +36,7 @@ export class AssetCore { | ||||
|       encodedVideoPath: null, | ||||
|       tags: [], | ||||
|       sharedLinks: [], | ||||
|       originalFileName: parse(file.originalName).name, | ||||
|     }); | ||||
| 
 | ||||
|     await this.jobRepository.queue({ name: JobName.ASSET_UPLOADED, data: { asset, fileName: file.originalName } }); | ||||
|  | ||||
| @ -10,9 +10,6 @@ export class CreateExifDto { | ||||
|   @IsOptional() | ||||
|   model?: string; | ||||
| 
 | ||||
|   @IsOptional() | ||||
|   imageName?: string; | ||||
| 
 | ||||
|   @IsOptional() | ||||
|   exifImageWidth?: number; | ||||
| 
 | ||||
|  | ||||
| @ -28,8 +28,8 @@ export class DownloadService { | ||||
|       let fileCount = 0; | ||||
|       let complete = true; | ||||
| 
 | ||||
|       for (const { id, originalPath, exifInfo } of assets) { | ||||
|         const name = `${exifInfo?.imageName || id}${extname(originalPath)}`; | ||||
|       for (const { originalPath, exifInfo, originalFileName } of assets) { | ||||
|         const name = `${originalFileName}${extname(originalPath)}`; | ||||
|         archive.file(originalPath, { name }); | ||||
|         totalSize += Number(exifInfo?.fileSizeInByte || 0); | ||||
|         fileCount++; | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import { | ||||
|   AssetCore, | ||||
|   getFileNameWithoutExtension, | ||||
|   IAssetRepository, | ||||
|   IAssetUploadedJob, | ||||
|   IBaseJob, | ||||
| @ -21,7 +20,6 @@ import { ExifDateTime, exiftool, Tags } from 'exiftool-vendored'; | ||||
| import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'; | ||||
| import { Duration } from 'luxon'; | ||||
| import fs from 'node:fs'; | ||||
| import path from 'path'; | ||||
| import sharp from 'sharp'; | ||||
| import { Repository } from 'typeorm/repository/Repository'; | ||||
| import { promisify } from 'util'; | ||||
| @ -79,7 +77,7 @@ export class MetadataExtractionProcessor { | ||||
|         : await this.assetRepository.getWithout(WithoutProperty.EXIF); | ||||
| 
 | ||||
|       for (const asset of assets) { | ||||
|         const fileName = asset.exifInfo?.imageName ?? getFileNameWithoutExtension(asset.originalPath); | ||||
|         const fileName = asset.originalFileName; | ||||
|         const name = asset.type === AssetType.VIDEO ? JobName.EXTRACT_VIDEO_METADATA : JobName.EXIF_EXTRACTION; | ||||
|         await this.jobRepository.queue({ name, data: { asset, fileName } }); | ||||
|       } | ||||
| @ -92,7 +90,6 @@ export class MetadataExtractionProcessor { | ||||
|   async extractExifInfo(job: Job<IAssetUploadedJob>) { | ||||
|     try { | ||||
|       let asset = job.data.asset; | ||||
|       const fileName = job.data.fileName; | ||||
|       const exifData = await exiftool.read<ImmichTags>(asset.originalPath).catch((e) => { | ||||
|         this.logger.warn(`The exifData parsing failed due to: ${e} on file ${asset.originalPath}`); | ||||
|         return null; | ||||
| @ -126,7 +123,6 @@ export class MetadataExtractionProcessor { | ||||
| 
 | ||||
|       const newExif = new ExifEntity(); | ||||
|       newExif.assetId = asset.id; | ||||
|       newExif.imageName = path.parse(fileName).name; | ||||
|       newExif.fileSizeInByte = fileSizeInBytes; | ||||
|       newExif.make = exifData?.Make || null; | ||||
|       newExif.model = exifData?.Model || null; | ||||
| @ -191,7 +187,6 @@ export class MetadataExtractionProcessor { | ||||
|   @Process({ name: JobName.EXTRACT_VIDEO_METADATA, concurrency: 2 }) | ||||
|   async extractVideoMetadata(job: Job<IAssetUploadedJob>) { | ||||
|     let asset = job.data.asset; | ||||
|     const fileName = job.data.fileName; | ||||
| 
 | ||||
|     if (!asset.isVisible) { | ||||
|       return; | ||||
| @ -219,7 +214,6 @@ export class MetadataExtractionProcessor { | ||||
|       const newExif = new ExifEntity(); | ||||
|       newExif.assetId = asset.id; | ||||
|       newExif.description = ''; | ||||
|       newExif.imageName = path.parse(fileName).name || null; | ||||
|       newExif.fileSizeInByte = data.format.size || null; | ||||
|       newExif.dateTimeOriginal = fileCreatedAt ? new Date(fileCreatedAt) : null; | ||||
|       newExif.modifyDate = null; | ||||
| @ -242,7 +236,6 @@ export class MetadataExtractionProcessor { | ||||
|         if (photoAsset) { | ||||
|           await this.assetCore.save({ id: photoAsset.id, livePhotoVideoId: asset.id }); | ||||
|           await this.assetCore.save({ id: asset.id, isVisible: false }); | ||||
|           newExif.imageName = (photoAsset.exifInfo as ExifEntity).imageName; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|  | ||||
| @ -3548,11 +3548,6 @@ | ||||
|             "nullable": true, | ||||
|             "default": null | ||||
|           }, | ||||
|           "imageName": { | ||||
|             "type": "string", | ||||
|             "nullable": true, | ||||
|             "default": null | ||||
|           }, | ||||
|           "exifImageWidth": { | ||||
|             "type": "number", | ||||
|             "nullable": true, | ||||
| @ -3712,6 +3707,9 @@ | ||||
|           "originalPath": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "originalFileName": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "resizePath": { | ||||
|             "type": "string", | ||||
|             "nullable": true | ||||
| @ -3767,6 +3765,7 @@ | ||||
|           "ownerId", | ||||
|           "deviceId", | ||||
|           "originalPath", | ||||
|           "originalFileName", | ||||
|           "resizePath", | ||||
|           "fileCreatedAt", | ||||
|           "fileModifiedAt", | ||||
|  | ||||
| @ -13,6 +13,7 @@ export class AssetResponseDto { | ||||
|   @ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType }) | ||||
|   type!: AssetType; | ||||
|   originalPath!: string; | ||||
|   originalFileName!: string; | ||||
|   resizePath!: string | null; | ||||
|   fileCreatedAt!: string; | ||||
|   fileModifiedAt!: string; | ||||
| @ -36,6 +37,7 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto { | ||||
|     deviceId: entity.deviceId, | ||||
|     type: entity.type, | ||||
|     originalPath: entity.originalPath, | ||||
|     originalFileName: entity.originalFileName, | ||||
|     resizePath: entity.resizePath, | ||||
|     fileCreatedAt: entity.fileCreatedAt, | ||||
|     fileModifiedAt: entity.fileModifiedAt, | ||||
| @ -60,6 +62,7 @@ export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto { | ||||
|     deviceId: entity.deviceId, | ||||
|     type: entity.type, | ||||
|     originalPath: entity.originalPath, | ||||
|     originalFileName: entity.originalFileName, | ||||
|     resizePath: entity.resizePath, | ||||
|     fileCreatedAt: entity.fileCreatedAt, | ||||
|     fileModifiedAt: entity.fileModifiedAt, | ||||
|  | ||||
| @ -4,7 +4,6 @@ import { ApiProperty } from '@nestjs/swagger'; | ||||
| export class ExifResponseDto { | ||||
|   make?: string | null = null; | ||||
|   model?: string | null = null; | ||||
|   imageName?: string | null = null; | ||||
|   exifImageWidth?: number | null = null; | ||||
|   exifImageHeight?: number | null = null; | ||||
| 
 | ||||
| @ -30,7 +29,6 @@ export function mapExif(entity: ExifEntity): ExifResponseDto { | ||||
|   return { | ||||
|     make: entity.make, | ||||
|     model: entity.model, | ||||
|     imageName: entity.imageName, | ||||
|     exifImageWidth: entity.exifImageWidth, | ||||
|     exifImageHeight: entity.exifImageHeight, | ||||
|     fileSizeInByte: entity.fileSizeInByte ? parseInt(entity.fileSizeInByte.toString()) : null, | ||||
|  | ||||
| @ -26,7 +26,7 @@ export class StorageTemplateService { | ||||
|     const { asset } = data; | ||||
| 
 | ||||
|     try { | ||||
|       const filename = asset.exifInfo?.imageName || asset.id; | ||||
|       const filename = asset.originalFileName || asset.id; | ||||
|       await this.moveAsset(asset, filename); | ||||
| 
 | ||||
|       // move motion part of live photo
 | ||||
| @ -56,7 +56,7 @@ export class StorageTemplateService { | ||||
|       for (const asset of assets) { | ||||
|         const livePhotoParentAsset = livePhotoMap[asset.id]; | ||||
|         // TODO: remove livePhoto specific stuff once upload is fixed
 | ||||
|         const filename = asset.exifInfo?.imageName || livePhotoParentAsset?.exifInfo?.imageName || asset.id; | ||||
|         const filename = asset.originalFileName || livePhotoParentAsset?.originalFileName || asset.id; | ||||
|         await this.moveAsset(asset, filename); | ||||
|       } | ||||
| 
 | ||||
|  | ||||
| @ -118,6 +118,7 @@ export const fileStub = { | ||||
| export const assetEntityStub = { | ||||
|   noResizePath: Object.freeze<AssetEntity>({ | ||||
|     id: 'asset-id', | ||||
|     originalFileName: 'asset_1.jpeg', | ||||
|     deviceAssetId: 'device-asset-id', | ||||
|     fileModifiedAt: '2023-02-23T05:06:29.716Z', | ||||
|     fileCreatedAt: '2023-02-23T05:06:29.716Z', | ||||
| @ -163,9 +164,11 @@ export const assetEntityStub = { | ||||
|     livePhotoVideoId: null, | ||||
|     tags: [], | ||||
|     sharedLinks: [], | ||||
|     originalFileName: 'asset-id.ext', | ||||
|   }), | ||||
|   video: Object.freeze<AssetEntity>({ | ||||
|     id: 'asset-id', | ||||
|     originalFileName: 'asset-id.ext', | ||||
|     deviceAssetId: 'device-asset-id', | ||||
|     fileModifiedAt: '2023-02-23T05:06:29.716Z', | ||||
|     fileCreatedAt: '2023-02-23T05:06:29.716Z', | ||||
| @ -320,7 +323,6 @@ export const albumStub = { | ||||
| const assetInfo: ExifResponseDto = { | ||||
|   make: 'camera-make', | ||||
|   model: 'camera-model', | ||||
|   imageName: 'fancy-image', | ||||
|   exifImageWidth: 500, | ||||
|   exifImageHeight: 500, | ||||
|   fileSizeInByte: 100, | ||||
| @ -347,6 +349,7 @@ const assetResponse: AssetResponseDto = { | ||||
|   deviceId: 'device_id_1', | ||||
|   type: AssetType.VIDEO, | ||||
|   originalPath: 'fake_path/jpeg', | ||||
|   originalFileName: 'asset_1.jpeg', | ||||
|   resizePath: '', | ||||
|   fileModifiedAt: today.toISOString(), | ||||
|   fileCreatedAt: today.toISOString(), | ||||
| @ -602,6 +605,7 @@ export const sharedLinkStub = { | ||||
|           isVisible: true, | ||||
|           livePhotoVideo: null, | ||||
|           livePhotoVideoId: null, | ||||
|           originalFileName: 'asset_1.jpeg', | ||||
|           exifInfo: { | ||||
|             livePhotoCID: null, | ||||
|             assetId: 'id_1', | ||||
| @ -620,7 +624,6 @@ export const sharedLinkStub = { | ||||
|             country: 'country', | ||||
|             make: 'camera-make', | ||||
|             model: 'camera-model', | ||||
|             imageName: 'fancy-image', | ||||
|             lensModel: 'fancy', | ||||
|             fNumber: 100, | ||||
|             focalLength: 100, | ||||
|  | ||||
| @ -87,6 +87,9 @@ export class AssetEntity { | ||||
|   @Column({ nullable: true }) | ||||
|   livePhotoVideoId!: string | null; | ||||
| 
 | ||||
|   @Column({ type: 'varchar' }) | ||||
|   originalFileName!: string; | ||||
| 
 | ||||
|   @OneToOne(() => ExifEntity, (exifEntity) => exifEntity.asset) | ||||
|   exifInfo?: ExifEntity; | ||||
| 
 | ||||
|  | ||||
| @ -63,9 +63,6 @@ export class ExifEntity { | ||||
|   @Column({ type: 'varchar', nullable: true }) | ||||
|   model!: string | null; | ||||
| 
 | ||||
|   @Column({ type: 'varchar', nullable: true }) | ||||
|   imageName!: string | null; | ||||
| 
 | ||||
|   @Column({ type: 'varchar', nullable: true }) | ||||
|   lensModel!: string | null; | ||||
| 
 | ||||
| @ -94,7 +91,6 @@ export class ExifEntity { | ||||
|                          COALESCE(model, '') || ' ' || | ||||
|                          COALESCE(orientation, '') || ' ' || | ||||
|                          COALESCE("lensModel", '') || ' ' || | ||||
|                          COALESCE("imageName", '') || ' ' || | ||||
|                          COALESCE("city", '') || ' ' || | ||||
|                          COALESCE("state", '') || ' ' || | ||||
|                          COALESCE("country", ''))`,
 | ||||
|  | ||||
| @ -0,0 +1,30 @@ | ||||
| import { MigrationInterface, QueryRunner } from 'typeorm'; | ||||
| 
 | ||||
| export class AddOriginalFileNameToAssetTable1681144628393 implements MigrationInterface { | ||||
|   name = 'AddOriginalFileNameToAssetTable1681144628393'; | ||||
| 
 | ||||
|   public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|     await queryRunner.query(`ALTER TABLE "assets" ADD "originalFileName" character varying`); | ||||
| 
 | ||||
|     await queryRunner.query(` | ||||
|     UPDATE assets a | ||||
|       SET "originalFileName" = ( | ||||
|         select e."imageName" | ||||
|         from exif e | ||||
|         where e."assetId" = a.id | ||||
|       ) | ||||
|     `);
 | ||||
| 
 | ||||
|     await queryRunner.query(` | ||||
|     UPDATE assets a | ||||
|       SET "originalFileName" = a.id | ||||
|       where a."originalFileName" IS NULL or a."originalFileName" = '' | ||||
|     `);
 | ||||
| 
 | ||||
|     await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "originalFileName" SET NOT NULL`); | ||||
|   } | ||||
| 
 | ||||
|   public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|     await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "originalFileName"`); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,62 @@ | ||||
| import { MigrationInterface, QueryRunner } from 'typeorm'; | ||||
| 
 | ||||
| export class RemoveImageNameFromEXIFTable1681159594469 implements MigrationInterface { | ||||
|   name = 'RemoveImageNameFromEXIFTable1681159594469'; | ||||
| 
 | ||||
|   public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|     await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN IF EXISTS "exifTextSearchableColumn"`); | ||||
|     await queryRunner.query( | ||||
|       `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`, | ||||
|       ['GENERATED_COLUMN', 'exifTextSearchableColumn', 'immich', 'public', 'exif'], | ||||
|     ); | ||||
|     await queryRunner.query(`ALTER TABLE "exif" ADD "exifTextSearchableColumn" tsvector GENERATED ALWAYS AS (TO_TSVECTOR('english',
 | ||||
|                      COALESCE(make, '') || ' ' || | ||||
|                      COALESCE(model, '') || ' ' || | ||||
|                      COALESCE(orientation, '') || ' ' || | ||||
|                      COALESCE("lensModel", '') || ' ' || | ||||
|                      COALESCE("city", '') || ' ' || | ||||
|                      COALESCE("state", '') || ' ' || | ||||
|                      COALESCE("country", ''))) STORED NOT NULL`);
 | ||||
|     await queryRunner.query( | ||||
|       `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES ($1, $2, $3, $4, $5, $6)`, | ||||
|       [ | ||||
|         'immich', | ||||
|         'public', | ||||
|         'exif', | ||||
|         'GENERATED_COLUMN', | ||||
|         'exifTextSearchableColumn', | ||||
|         "TO_TSVECTOR('english',\n                         COALESCE(make, '') || ' ' ||\n                         COALESCE(model, '') || ' ' ||\n                         COALESCE(orientation, '') || ' ' ||\n                         COALESCE(\"lensModel\", '') || ' ' ||\n                         COALESCE(\"city\", '') || ' ' ||\n                         COALESCE(\"state\", '') || ' ' ||\n                         COALESCE(\"country\", ''))", | ||||
|       ], | ||||
|     ); | ||||
|     await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "imageName"`); | ||||
|   } | ||||
| 
 | ||||
|   public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|     await queryRunner.query( | ||||
|       `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`, | ||||
|       ['GENERATED_COLUMN', 'exifTextSearchableColumn', 'immich', 'public', 'exif'], | ||||
|     ); | ||||
|     await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exifTextSearchableColumn"`); | ||||
|     await queryRunner.query( | ||||
|       `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES ($1, $2, $3, $4, $5, $6)`, | ||||
|       [ | ||||
|         'immich', | ||||
|         'public', | ||||
|         'exif', | ||||
|         'GENERATED_COLUMN', | ||||
|         'exifTextSearchableColumn', | ||||
|         "TO_TSVECTOR('english',\n                         COALESCE(make, '') || ' ' ||\n                         COALESCE(model, '') || ' ' ||\n                         COALESCE(orientation, '') || ' ' ||\n                         COALESCE(\"lensModel\", '') || ' ' ||\n                         COALESCE(\"imageName\", '') || ' ' ||\n                         COALESCE(\"city\", '') || ' ' ||\n                         COALESCE(\"state\", '') || ' ' ||\n                         COALESCE(\"country\", ''))", | ||||
|       ], | ||||
|     ); | ||||
|     await queryRunner.query(`ALTER TABLE "exif" ADD "exifTextSearchableColumn" tsvector GENERATED ALWAYS AS (TO_TSVECTOR('english',
 | ||||
|                          COALESCE(make, '') || ' ' || | ||||
|                          COALESCE(model, '') || ' ' || | ||||
|                          COALESCE(orientation, '') || ' ' || | ||||
|                          COALESCE("lensModel", '') || ' ' || | ||||
|                          COALESCE("imageName", '') || ' ' || | ||||
|                          COALESCE("city", '') || ' ' || | ||||
|                          COALESCE("state", '') || ' ' || | ||||
|                          COALESCE("country", ''))) STORED NOT NULL`);
 | ||||
|     await queryRunner.query(`ALTER TABLE "exif" ADD "imageName" character varying`); | ||||
|   } | ||||
| } | ||||
| @ -144,7 +144,7 @@ export class TypesenseRepository implements ISearchRepository { | ||||
| 
 | ||||
|     const { facet_counts: facets } = await asset$.search({ | ||||
|       ...common, | ||||
|       query_by: 'exifInfo.imageName', | ||||
|       query_by: 'originalFileName', | ||||
|       facet_by: 'exifInfo.city,smartInfo.objects', | ||||
|       max_facet_values: 12, | ||||
|     }); | ||||
| @ -157,7 +157,7 @@ export class TypesenseRepository implements ISearchRepository { | ||||
|               mergeMap((count) => { | ||||
|                 const config = { | ||||
|                   ...common, | ||||
|                   query_by: 'exifInfo.imageName', | ||||
|                   query_by: 'originalFileName', | ||||
|                   filter_by: [ | ||||
|                     this.buildFilterBy('ownerId', userId, true), | ||||
|                     this.buildFilterBy(facet.field_name, count.value, true), | ||||
| @ -230,7 +230,7 @@ export class TypesenseRepository implements ISearchRepository { | ||||
|       .search({ | ||||
|         q: query, | ||||
|         query_by: [ | ||||
|           'exifInfo.imageName', | ||||
|           'originalFileName', | ||||
|           'exifInfo.country', | ||||
|           'exifInfo.state', | ||||
|           'exifInfo.city', | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { CollectionCreateSchema } from 'typesense/lib/Typesense/Collections'; | ||||
| 
 | ||||
| export const assetSchemaVersion = 3; | ||||
| export const assetSchemaVersion = 4; | ||||
| export const assetSchema: CollectionCreateSchema = { | ||||
|   name: `assets-v${assetSchemaVersion}`, | ||||
|   fields: [ | ||||
| @ -13,6 +13,7 @@ export const assetSchema: CollectionCreateSchema = { | ||||
|     { name: 'fileCreatedAt', type: 'string', facet: false, sort: true }, | ||||
|     { name: 'fileModifiedAt', type: 'string', facet: false, sort: true }, | ||||
|     { name: 'isFavorite', type: 'bool', facet: true }, | ||||
|     { name: 'originalFileName', type: 'string', facet: false, optional: true }, | ||||
|     // { name: 'checksum', type: 'string', facet: true },
 | ||||
|     // { name: 'tags', type: 'string[]', facet: true, optional: true },
 | ||||
| 
 | ||||
| @ -21,7 +22,6 @@ export const assetSchema: CollectionCreateSchema = { | ||||
|     { name: 'exifInfo.country', type: 'string', facet: true, optional: true }, | ||||
|     { name: 'exifInfo.state', type: 'string', facet: true, optional: true }, | ||||
|     { name: 'exifInfo.description', type: 'string', facet: false, optional: true }, | ||||
|     { name: 'exifInfo.imageName', type: 'string', facet: false, optional: true }, | ||||
|     { name: 'exifInfo.make', type: 'string', facet: true, optional: true }, | ||||
|     { name: 'exifInfo.model', type: 'string', facet: true, optional: true }, | ||||
|     { name: 'exifInfo.orientation', type: 'string', optional: true }, | ||||
|  | ||||
							
								
								
									
										12
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @ -476,6 +476,12 @@ export interface AssetResponseDto { | ||||
|      * @memberof AssetResponseDto | ||||
|      */ | ||||
|     'originalPath': string; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
|      * @memberof AssetResponseDto | ||||
|      */ | ||||
|     'originalFileName': string; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -1100,12 +1106,6 @@ export interface ExifResponseDto { | ||||
|      * @memberof ExifResponseDto | ||||
|      */ | ||||
|     'model'?: string | null; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
|      * @memberof ExifResponseDto | ||||
|      */ | ||||
|     'imageName'?: string | null; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|  | ||||
| @ -96,7 +96,7 @@ | ||||
| 				<div><ImageOutline size="24" /></div> | ||||
| 
 | ||||
| 				<div> | ||||
| 					<p>{`${asset.exifInfo.imageName}.${asset.originalPath.split('.')[1]}` || ''}</p> | ||||
| 					<p>{`${asset.originalFileName}.${asset.originalPath.split('.')[1]}` || ''}</p> | ||||
| 					<div class="flex text-sm gap-2"> | ||||
| 						{#if asset.exifInfo.exifImageHeight && asset.exifInfo.exifImageWidth} | ||||
| 							{#if getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)} | ||||
|  | ||||
| @ -116,7 +116,7 @@ | ||||
| 
 | ||||
| 				<ImageThumbnail | ||||
| 					url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)} | ||||
| 					altText={asset.exifInfo?.imageName ?? asset.id} | ||||
| 					altText={asset.originalFileName} | ||||
| 					widthStyle="{width}px" | ||||
| 					heightStyle="{height}px" | ||||
| 				/> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user