mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feat(server): add originalFileName to asset table (#2231)
This commit is contained in:
parent
db628cec11
commit
a1a62b00a0
24 changed files with 143 additions and 60 deletions
|
|
@ -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 },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue