diff --git a/mobile/openapi/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md index 44754a6c4d..0c8bb3363e 100644 --- a/mobile/openapi/doc/AssetResponseDto.md +++ b/mobile/openapi/doc/AssetResponseDto.md @@ -14,6 +14,7 @@ Name | Type | Description | Notes **ownerId** | **String** | | **deviceId** | **String** | | **originalPath** | **String** | | +**originalFileName** | **String** | | **resizePath** | **String** | | **fileCreatedAt** | **String** | | **fileModifiedAt** | **String** | | diff --git a/mobile/openapi/doc/ExifResponseDto.md b/mobile/openapi/doc/ExifResponseDto.md index 7c40bc742a..1fb483d635 100644 --- a/mobile/openapi/doc/ExifResponseDto.md +++ b/mobile/openapi/doc/ExifResponseDto.md @@ -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] diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 013ac00ff4..fbe790190d 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -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', diff --git a/mobile/openapi/lib/model/exif_response_dto.dart b/mobile/openapi/lib/model/exif_response_dto.dart index 071d8caf70..0c3ec07461 100644 --- a/mobile/openapi/lib/model/exif_response_dto.dart +++ b/mobile/openapi/lib/model/exif_response_dto.dart @@ -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()), diff --git a/mobile/openapi/test/asset_response_dto_test.dart b/mobile/openapi/test/asset_response_dto_test.dart index 35a98737b9..b5c72242d6 100644 --- a/mobile/openapi/test/asset_response_dto_test.dart +++ b/mobile/openapi/test/asset_response_dto_test.dart @@ -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 diff --git a/mobile/openapi/test/exif_response_dto_test.dart b/mobile/openapi/test/exif_response_dto_test.dart index 63623cd4fe..138bff9616 100644 --- a/mobile/openapi/test/exif_response_dto_test.dart +++ b/mobile/openapi/test/exif_response_dto_test.dart @@ -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 diff --git a/server/apps/immich/src/api-v1/asset/asset.core.ts b/server/apps/immich/src/api-v1/asset/asset.core.ts index 0798d82f74..14261b001e 100644 --- a/server/apps/immich/src/api-v1/asset/asset.core.ts +++ b/server/apps/immich/src/api-v1/asset/asset.core.ts @@ -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 } }); diff --git a/server/apps/immich/src/api-v1/asset/dto/create-exif.dto.ts b/server/apps/immich/src/api-v1/asset/dto/create-exif.dto.ts index 16a7d6f226..c2594c47ba 100644 --- a/server/apps/immich/src/api-v1/asset/dto/create-exif.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/create-exif.dto.ts @@ -10,9 +10,6 @@ export class CreateExifDto { @IsOptional() model?: string; - @IsOptional() - imageName?: string; - @IsOptional() exifImageWidth?: number; diff --git a/server/apps/immich/src/modules/download/download.service.ts b/server/apps/immich/src/modules/download/download.service.ts index d5e800578b..c11377407c 100644 --- a/server/apps/immich/src/modules/download/download.service.ts +++ b/server/apps/immich/src/modules/download/download.service.ts @@ -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++; diff --git a/server/apps/microservices/src/processors/metadata-extraction.processor.ts b/server/apps/microservices/src/processors/metadata-extraction.processor.ts index 43e1ac98d8..5874a231d6 100644 --- a/server/apps/microservices/src/processors/metadata-extraction.processor.ts +++ b/server/apps/microservices/src/processors/metadata-extraction.processor.ts @@ -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; } } diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 7318739e24..c699528caa 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -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", diff --git a/server/libs/domain/src/asset/response-dto/asset-response.dto.ts b/server/libs/domain/src/asset/response-dto/asset-response.dto.ts index d057dabad2..041aef5829 100644 --- a/server/libs/domain/src/asset/response-dto/asset-response.dto.ts +++ b/server/libs/domain/src/asset/response-dto/asset-response.dto.ts @@ -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, diff --git a/server/libs/domain/src/asset/response-dto/exif-response.dto.ts b/server/libs/domain/src/asset/response-dto/exif-response.dto.ts index 83961a1960..cac1d24686 100644 --- a/server/libs/domain/src/asset/response-dto/exif-response.dto.ts +++ b/server/libs/domain/src/asset/response-dto/exif-response.dto.ts @@ -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, diff --git a/server/libs/domain/src/storage-template/storage-template.service.ts b/server/libs/domain/src/storage-template/storage-template.service.ts index b6df125c18..ff188d29e1 100644 --- a/server/libs/domain/src/storage-template/storage-template.service.ts +++ b/server/libs/domain/src/storage-template/storage-template.service.ts @@ -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); } diff --git a/server/libs/domain/test/fixtures.ts b/server/libs/domain/test/fixtures.ts index af5ca08ec5..00bcfee90e 100644 --- a/server/libs/domain/test/fixtures.ts +++ b/server/libs/domain/test/fixtures.ts @@ -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, diff --git a/server/libs/infra/src/entities/asset.entity.ts b/server/libs/infra/src/entities/asset.entity.ts index ba83e62542..f47e0fec39 100644 --- a/server/libs/infra/src/entities/asset.entity.ts +++ b/server/libs/infra/src/entities/asset.entity.ts @@ -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; diff --git a/server/libs/infra/src/entities/exif.entity.ts b/server/libs/infra/src/entities/exif.entity.ts index 00b57f9e28..d4abd6e6d7 100644 --- a/server/libs/infra/src/entities/exif.entity.ts +++ b/server/libs/infra/src/entities/exif.entity.ts @@ -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", ''))`, diff --git a/server/libs/infra/src/migrations/1681144628393-AddOriginalFileNameToAssetTable.ts b/server/libs/infra/src/migrations/1681144628393-AddOriginalFileNameToAssetTable.ts new file mode 100644 index 0000000000..7c547108b5 --- /dev/null +++ b/server/libs/infra/src/migrations/1681144628393-AddOriginalFileNameToAssetTable.ts @@ -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"`); + } +} diff --git a/server/libs/infra/src/migrations/1681159594469-RemoveImageNameFromEXIFTable.ts b/server/libs/infra/src/migrations/1681159594469-RemoveImageNameFromEXIFTable.ts new file mode 100644 index 0000000000..87bf2a62e6 --- /dev/null +++ b/server/libs/infra/src/migrations/1681159594469-RemoveImageNameFromEXIFTable.ts @@ -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`); + } +} diff --git a/server/libs/infra/src/repositories/typesense.repository.ts b/server/libs/infra/src/repositories/typesense.repository.ts index 6e3a2115f8..d4682ee27f 100644 --- a/server/libs/infra/src/repositories/typesense.repository.ts +++ b/server/libs/infra/src/repositories/typesense.repository.ts @@ -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', diff --git a/server/libs/infra/src/typesense-schemas/asset.schema.ts b/server/libs/infra/src/typesense-schemas/asset.schema.ts index 28a61cf097..04636d4f67 100644 --- a/server/libs/infra/src/typesense-schemas/asset.schema.ts +++ b/server/libs/infra/src/typesense-schemas/asset.schema.ts @@ -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 }, diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index b1e38dae25..f92dd2328e 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -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} diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 9ab4d99a1b..d0dd9a8661 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -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)} diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index c4239f9849..39a17faa19 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -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" />