mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
fix: time zone upserts (#25889)
This commit is contained in:
parent
27a2808470
commit
9c098109e0
7 changed files with 69 additions and 7 deletions
|
|
@ -473,6 +473,7 @@ describe('/asset', () => {
|
||||||
id: user1Assets[0].id,
|
id: user1Assets[0].id,
|
||||||
exifInfo: expect.objectContaining({
|
exifInfo: expect.objectContaining({
|
||||||
dateTimeOriginal: '2023-11-20T01:11:00+00:00',
|
dateTimeOriginal: '2023-11-20T01:11:00+00:00',
|
||||||
|
timeZone: 'UTC-7',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import {
|
||||||
onBeforeUnlink,
|
onBeforeUnlink,
|
||||||
} from 'src/utils/asset.util';
|
} from 'src/utils/asset.util';
|
||||||
import { updateLockedColumns } from 'src/utils/database';
|
import { updateLockedColumns } from 'src/utils/database';
|
||||||
|
import { extractTimeZone } from 'src/utils/date';
|
||||||
import { transformOcrBoundingBox } from 'src/utils/transform';
|
import { transformOcrBoundingBox } from 'src/utils/transform';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -168,12 +169,13 @@ export class AssetService extends BaseService {
|
||||||
},
|
},
|
||||||
_.isUndefined,
|
_.isUndefined,
|
||||||
);
|
);
|
||||||
const extractedTimeZone = dateTimeOriginal ? DateTime.fromISO(dateTimeOriginal, { setZone: true }).zone : undefined;
|
|
||||||
|
|
||||||
if (Object.keys(exifDto).length > 0) {
|
if (Object.keys(exifDto).length > 0) {
|
||||||
await this.assetRepository.updateAllExif(ids, exifDto);
|
await this.assetRepository.updateAllExif(ids, exifDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extractedTimeZone = extractTimeZone(dateTimeOriginal);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(dateTimeRelative !== undefined && dateTimeRelative !== 0) ||
|
(dateTimeRelative !== undefined && dateTimeRelative !== 0) ||
|
||||||
timeZone !== undefined ||
|
timeZone !== undefined ||
|
||||||
|
|
@ -513,12 +515,11 @@ export class AssetService extends BaseService {
|
||||||
rating?: number;
|
rating?: number;
|
||||||
}) {
|
}) {
|
||||||
const { id, description, dateTimeOriginal, latitude, longitude, rating } = dto;
|
const { id, description, dateTimeOriginal, latitude, longitude, rating } = dto;
|
||||||
const extractedTimeZone = dateTimeOriginal ? DateTime.fromISO(dateTimeOriginal, { setZone: true }).zone : undefined;
|
|
||||||
const writes = _.omitBy(
|
const writes = _.omitBy(
|
||||||
{
|
{
|
||||||
description,
|
description,
|
||||||
dateTimeOriginal,
|
dateTimeOriginal,
|
||||||
timeZone: extractedTimeZone?.type === 'fixed' ? extractedTimeZone.name : undefined,
|
timeZone: extractTimeZone(dateTimeOriginal)?.name,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
rating,
|
rating,
|
||||||
|
|
|
||||||
|
|
@ -1766,13 +1766,14 @@ describe(MetadataService.name, () => {
|
||||||
const asset = factory.jobAssets.sidecarWrite();
|
const asset = factory.jobAssets.sidecarWrite();
|
||||||
const description = 'this is a description';
|
const description = 'this is a description';
|
||||||
const gps = 12;
|
const gps = 12;
|
||||||
const date = '2023-11-22T04:56:12.196Z';
|
const date = '2023-11-21T22:56:12.196-06:00';
|
||||||
|
|
||||||
mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue([
|
mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue([
|
||||||
'description',
|
'description',
|
||||||
'latitude',
|
'latitude',
|
||||||
'longitude',
|
'longitude',
|
||||||
'dateTimeOriginal',
|
'dateTimeOriginal',
|
||||||
|
'timeZone',
|
||||||
]);
|
]);
|
||||||
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(asset);
|
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(asset);
|
||||||
await expect(
|
await expect(
|
||||||
|
|
@ -1792,6 +1793,7 @@ describe(MetadataService.name, () => {
|
||||||
'latitude',
|
'latitude',
|
||||||
'longitude',
|
'longitude',
|
||||||
'dateTimeOriginal',
|
'dateTimeOriginal',
|
||||||
|
'timeZone',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { BaseService } from 'src/services/base.service';
|
||||||
import { JobItem, JobOf } from 'src/types';
|
import { JobItem, JobOf } from 'src/types';
|
||||||
import { getAssetFiles } from 'src/utils/asset.util';
|
import { getAssetFiles } from 'src/utils/asset.util';
|
||||||
import { isAssetChecksumConstraint } from 'src/utils/database';
|
import { isAssetChecksumConstraint } from 'src/utils/database';
|
||||||
|
import { mergeTimeZone } from 'src/utils/date';
|
||||||
import { mimeTypes } from 'src/utils/mime-types';
|
import { mimeTypes } from 'src/utils/mime-types';
|
||||||
import { isFaceImportEnabled } from 'src/utils/misc';
|
import { isFaceImportEnabled } from 'src/utils/misc';
|
||||||
import { upsertTags } from 'src/utils/tag';
|
import { upsertTags } from 'src/utils/tag';
|
||||||
|
|
@ -431,14 +432,16 @@ export class MetadataService extends BaseService {
|
||||||
const { sidecarFile } = getAssetFiles(asset.files);
|
const { sidecarFile } = getAssetFiles(asset.files);
|
||||||
const sidecarPath = sidecarFile?.path || `${asset.originalPath}.xmp`;
|
const sidecarPath = sidecarFile?.path || `${asset.originalPath}.xmp`;
|
||||||
|
|
||||||
const { description, dateTimeOriginal, latitude, longitude, rating, tags } = _.pick(
|
const { description, dateTimeOriginal, latitude, longitude, rating, tags, timeZone } = _.pick(
|
||||||
{
|
{
|
||||||
description: asset.exifInfo.description,
|
description: asset.exifInfo.description,
|
||||||
dateTimeOriginal: asset.exifInfo.dateTimeOriginal,
|
// the kysely type is wrong here; fixed in 0.28.3
|
||||||
|
dateTimeOriginal: asset.exifInfo.dateTimeOriginal as string | null,
|
||||||
latitude: asset.exifInfo.latitude,
|
latitude: asset.exifInfo.latitude,
|
||||||
longitude: asset.exifInfo.longitude,
|
longitude: asset.exifInfo.longitude,
|
||||||
rating: asset.exifInfo.rating,
|
rating: asset.exifInfo.rating,
|
||||||
tags: asset.exifInfo.tags,
|
tags: asset.exifInfo.tags,
|
||||||
|
timeZone: asset.exifInfo.timeZone,
|
||||||
},
|
},
|
||||||
lockedProperties,
|
lockedProperties,
|
||||||
);
|
);
|
||||||
|
|
@ -447,7 +450,7 @@ export class MetadataService extends BaseService {
|
||||||
<Tags>{
|
<Tags>{
|
||||||
Description: description,
|
Description: description,
|
||||||
ImageDescription: description,
|
ImageDescription: description,
|
||||||
DateTimeOriginal: dateTimeOriginal ? String(dateTimeOriginal) : undefined,
|
DateTimeOriginal: mergeTimeZone(dateTimeOriginal, timeZone)?.toISO(),
|
||||||
GPSLatitude: latitude,
|
GPSLatitude: latitude,
|
||||||
GPSLongitude: longitude,
|
GPSLongitude: longitude,
|
||||||
Rating: rating,
|
Rating: rating,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
export const asDateString = (x: Date | string | null): string | null => {
|
export const asDateString = (x: Date | string | null): string | null => {
|
||||||
return x instanceof Date ? x.toISOString().split('T')[0] : x;
|
return x instanceof Date ? x.toISOString().split('T')[0] : x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const extractTimeZone = (dateTimeOriginal?: string | null) => {
|
||||||
|
const extractedTimeZone = dateTimeOriginal ? DateTime.fromISO(dateTimeOriginal, { setZone: true }).zone : undefined;
|
||||||
|
return extractedTimeZone?.type === 'fixed' ? extractedTimeZone : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mergeTimeZone = (dateTimeOriginal?: string | null, timeZone?: string | null) => {
|
||||||
|
return dateTimeOriginal
|
||||||
|
? DateTime.fromISO(dateTimeOriginal, { zone: 'UTC' }).setZone(timeZone ?? undefined)
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -456,6 +456,47 @@ describe(AssetService.name, () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should relatively update an assets with timezone', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newExif({ assetId: asset.id, dateTimeOriginal: '2023-11-19T18:11:00', timeZone: 'UTC+5' });
|
||||||
|
|
||||||
|
await sut.updateAll(auth, { ids: [asset.id], dateTimeRelative: -1441 });
|
||||||
|
|
||||||
|
await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
exifInfo: expect.objectContaining({
|
||||||
|
dateTimeOriginal: '2023-11-18T18:10:00+00:00',
|
||||||
|
timeZone: 'UTC+5',
|
||||||
|
lockedProperties: ['timeZone', 'dateTimeOriginal'],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should relatively update an assets and set a timezone', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newExif({ assetId: asset.id, dateTimeOriginal: '2023-11-19T18:11:00' });
|
||||||
|
|
||||||
|
await sut.updateAll(auth, { ids: [asset.id], dateTimeRelative: -11, timeZone: 'UTC+5' });
|
||||||
|
|
||||||
|
await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
exifInfo: expect.objectContaining({
|
||||||
|
dateTimeOriginal: '2023-11-19T18:00:00+00:00',
|
||||||
|
timeZone: 'UTC+5',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should update dateTimeOriginal', async () => {
|
it('should update dateTimeOriginal', async () => {
|
||||||
const { sut, ctx } = setup();
|
const { sut, ctx } = setup();
|
||||||
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
|
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
|
||||||
|
|
|
||||||
|
|
@ -361,6 +361,7 @@ const assetSidecarWriteFactory = () => {
|
||||||
latitude: 12,
|
latitude: 12,
|
||||||
longitude: 12,
|
longitude: 12,
|
||||||
dateTimeOriginal: '2023-11-22T04:56:12.196Z',
|
dateTimeOriginal: '2023-11-22T04:56:12.196Z',
|
||||||
|
timeZone: 'UTC-6',
|
||||||
} as unknown as Exif,
|
} as unknown as Exif,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue