1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-23 12:12:45 +01:00
This commit is contained in:
Jonathan Jogenfors 2025-01-13 13:27:25 +01:00
parent 987f8796a8
commit 8bc9b668a0
17 changed files with 186 additions and 170 deletions

View file

@ -716,7 +716,7 @@ describe('/libraries', () => {
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets).toEqual(assetsBefore);
expect(assets.items.map((asset) => asset.id)).toEqual(assetsBefore.items.map((asset) => asset.id));
});
describe('xmp metadata', async () => {
@ -735,12 +735,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2000-09-27T12:35:33.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2000-09-27T12:35:33+00:00',
}),
}),
]);
@ -761,12 +766,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2000-09-27T12:35:33.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2000-09-27T12:35:33+00:00',
}),
}),
]);
@ -788,12 +798,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2000-09-27T12:35:33.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2000-09-27T12:35:33+00:00',
}),
}),
]);
@ -824,12 +839,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2010-09-27T12:35:33.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2010-09-27T12:35:33+00:00',
}),
}),
]);
@ -858,12 +878,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2000-09-27T12:35:33.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2000-09-27T12:35:33+00:00',
}),
}),
]);
@ -892,12 +917,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2000-09-27T12:35:33.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2000-09-27T12:35:33+00:00',
}),
}),
]);
@ -928,12 +958,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2010-09-27T12:35:33.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2010-09-27T12:35:33+00:00',
}),
}),
]);
@ -963,12 +998,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2010-07-20T17:27:12.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2010-07-20T17:27:12+00:00',
}),
}),
]);
@ -998,12 +1038,17 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
withExif: true,
});
expect(newAssets.items).toEqual([
expect.objectContaining({
originalFileName: 'glarus.nef',
fileCreatedAt: '2010-07-20T17:27:12.000Z',
exifInfo: expect.objectContaining({
dateTimeOriginal: '2010-07-20T17:27:12+00:00',
}),
}),
]);

BIN
mobile/openapi/README.md generated

Binary file not shown.

Binary file not shown.

View file

@ -2853,9 +2853,44 @@
]
}
},
"/libraries/{id}/count": {
"/libraries/{id}/scan": {
"post": {
"operationId": "scanLibrary",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Libraries"
]
}
},
"/libraries/{id}/statistics": {
"get": {
"operationId": "getAssetCount",
"operationId": "getLibraryStatistics",
"parameters": [
{
"name": "id",
@ -2895,41 +2930,6 @@
]
}
},
"/libraries/{id}/scan": {
"post": {
"operationId": "scanLibrary",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Libraries"
]
}
},
"/libraries/{id}/validate": {
"post": {
"operationId": "validate",
@ -8450,12 +8450,10 @@
},
"fileCreatedAt": {
"format": "date-time",
"nullable": true,
"type": "string"
},
"fileModifiedAt": {
"format": "date-time",
"nullable": true,
"type": "string"
},
"hasMetadata": {
@ -8488,7 +8486,6 @@
},
"localDateTime": {
"format": "date-time",
"nullable": true,
"type": "string"
},
"originalFileName": {

View file

@ -2093,16 +2093,6 @@ export function updateLibrary({ id, updateLibraryDto }: {
body: updateLibraryDto
})));
}
export function getAssetCount({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: number;
}>(`/libraries/${encodeURIComponent(id)}/count`, {
...opts
}));
}
export function scanLibrary({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
@ -2111,6 +2101,16 @@ export function scanLibrary({ id }: {
method: "POST"
}));
}
export function getLibraryStatistics({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: number;
}>(`/libraries/${encodeURIComponent(id)}/statistics`, {
...opts
}));
}
export function validate({ id, validateLibraryDto }: {
id: string;
validateLibraryDto: ValidateLibraryDto;

View file

@ -56,9 +56,9 @@ export class LibraryController {
return this.service.validate(id, dto);
}
@Get(':id/count')
@Get(':id/statistics')
@Authenticated({ permission: Permission.LIBRARY_STATISTICS, admin: true })
getAssetCount(@Param() { id }: UUIDParamDto): Promise<number> {
getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<number> {
return this.service.getAssetCount(id);
}

6
server/src/db.d.ts vendored
View file

@ -121,8 +121,8 @@ export interface Assets {
duplicateId: string | null;
duration: string | null;
encodedVideoPath: Generated<string | null>;
fileCreatedAt: Timestamp | null;
fileModifiedAt: Timestamp | null;
fileCreatedAt: Timestamp;
fileModifiedAt: Timestamp;
id: Generated<string>;
isArchived: Generated<boolean>;
isExternal: Generated<boolean>;
@ -131,7 +131,7 @@ export interface Assets {
isVisible: Generated<boolean>;
libraryId: string | null;
livePhotoVideoId: string | null;
localDateTime: Timestamp | null;
localDateTime: Timestamp;
originalFileName: string;
originalPath: string;
ownerId: string;

View file

@ -165,8 +165,8 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt
const hasSharedLink = entity.sharedLinks?.length > 0;
const hasSharedUser = sharedUsers.length > 0;
let startDate = getAssetDateTime(assets.at(0)) ?? undefined;
let endDate = getAssetDateTime(assets.at(-1)) ?? undefined;
let startDate = getAssetDateTime(assets.at(0));
let endDate = getAssetDateTime(assets.at(-1));
// Swap dates if start date is greater than end date.
if (startDate && endDate && startDate > endDate) {
[startDate, endDate] = [endDate, startDate];

View file

@ -21,7 +21,7 @@ export class SanitizedAssetResponseDto {
type!: AssetType;
thumbhash!: string | null;
originalMimeType?: string;
localDateTime!: Date | null;
localDateTime!: Date;
duration!: string;
livePhotoVideoId?: string | null;
hasMetadata!: boolean;
@ -36,8 +36,8 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
libraryId?: string | null;
originalPath!: string;
originalFileName!: string;
fileCreatedAt!: Date | null;
fileModifiedAt!: Date | null;
fileCreatedAt!: Date;
fileModifiedAt!: Date;
updatedAt!: Date;
isFavorite!: boolean;
isArchived!: boolean;

View file

@ -100,14 +100,14 @@ export class AssetEntity {
deletedAt!: Date | null;
@Index('idx_asset_file_created_at')
@Column({ type: 'timestamptz', nullable: true })
fileCreatedAt!: Date | null;
@Column({ type: 'timestamptz' })
fileCreatedAt!: Date;
@Column({ type: 'timestamptz', nullable: true })
localDateTime!: Date | null;
@Column({ type: 'timestamptz' })
localDateTime!: Date;
@Column({ type: 'timestamptz', nullable: true })
fileModifiedAt!: Date | null;
@Column({ type: 'timestamptz' })
fileModifiedAt!: Date;
@Column({ type: 'boolean', default: false })
isFavorite!: boolean;

View file

@ -1,18 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class NullableDates1736718596137 implements MigrationInterface {
name = 'NullableDates1736718596137'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "fileCreatedAt" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "localDateTime" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "fileModifiedAt" DROP NOT NULL`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "fileModifiedAt" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "localDateTime" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "fileCreatedAt" SET NOT NULL`);
}
}

View file

@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { CompiledQuery, Insertable, Kysely, UpdateResult, Updateable, sql } from 'kysely';
import { Insertable, Kysely, UpdateResult, Updateable, sql } from 'kysely';
import { isEmpty, isUndefined, omitBy } from 'lodash';
import { InjectKysely } from 'nestjs-kysely';
import { ASSET_FILE_CONFLICT_KEYS, EXIF_CONFLICT_KEYS, JOB_STATUS_CONFLICT_KEYS } from 'src/constants';

View file

@ -546,7 +546,7 @@ describe(AssetMediaService.name, () => {
files: [
{
assetId: assetStub.image.id,
createdAt: assetStub.image.fileCreatedAt,
createdAt: assetStub.image.fileCreatedAt ?? new Date(),
id: '42',
path: '/path/to/preview',
type: AssetFileType.THUMBNAIL,
@ -566,7 +566,7 @@ describe(AssetMediaService.name, () => {
files: [
{
assetId: assetStub.image.id,
createdAt: assetStub.image.fileCreatedAt,
createdAt: assetStub.image.fileCreatedAt ?? new Date(),
id: '42',
path: '/path/to/preview.jpg',
type: AssetFileType.PREVIEW,

View file

@ -229,11 +229,10 @@ export class LibraryService extends BaseService {
let progressMessage = '';
if (job.progressCounter && job.totalAssets) {
progressMessage = `(${job.progressCounter} of ${job.totalAssets})`;
} else {
progressMessage = `(${job.progressCounter} done so far)`;
}
progressMessage =
job.progressCounter && job.totalAssets
? `(${job.progressCounter} of ${job.totalAssets})`
: `(${job.progressCounter} done so far)`;
this.logger.log(`Imported ${assetIds.length} ${progressMessage} file(s) into library ${job.libraryId}`);
@ -362,10 +361,9 @@ export class LibraryService extends BaseService {
checksum: this.cryptoRepository.hashSha1(`path:${assetPath}`),
originalPath: assetPath,
// These dates are placeholders and will be read from disk during metadata extraction
fileCreatedAt: null,
fileModifiedAt: null,
localDateTime: null,
fileCreatedAt: new Date(),
fileModifiedAt: new Date(),
localDateTime: new Date(),
// TODO: device asset id is deprecated, remove it
deviceAssetId: `${basename(assetPath)}`.replaceAll(/\s+/g, ''),
deviceId: 'Library Import',
@ -480,8 +478,8 @@ export class LibraryService extends BaseService {
});
await this.queuePostSyncJobs(assetIdsToOnline);
if (progressMessage !== '') {
progressMessage + ', ';
if (progressMessage) {
progressMessage += ', ';
}
progressMessage += `${assetIdsToOnline.length} onlined`;
@ -491,8 +489,8 @@ export class LibraryService extends BaseService {
//TODO: When we have asset status, we need to leave deletedAt as is when status is trashed
await this.queuePostSyncJobs(assetIdsToUpdate);
if (progressMessage !== '') {
progressMessage + ', ';
if (progressMessage) {
progressMessage += ', ';
}
progressMessage += `${assetIdsToUpdate.length} updated`;
@ -501,8 +499,8 @@ export class LibraryService extends BaseService {
const remainingCount = assets.length - assetIdsToOffline.length - assetIdsToUpdate.length - assetIdsToOnline.length;
if (remainingCount) {
if (progressMessage !== '') {
progressMessage + ', ';
if (progressMessage) {
progressMessage += ', ';
}
progressMessage += `${remainingCount} unchanged`;
@ -523,7 +521,7 @@ export class LibraryService extends BaseService {
return JobStatus.SUCCESS;
}
private async checkOfflineAsset(asset: AssetEntity, library: LibraryEntity): Promise<boolean> {
private checkOfflineAsset(asset: AssetEntity, library: LibraryEntity): boolean {
if (!asset.libraryId) {
return false;
}
@ -567,7 +565,7 @@ export class LibraryService extends BaseService {
}
const mtime = stat.mtime;
const isAssetTimeUpdated = asset.fileModifiedAt ? mtime.toISOString() !== asset.fileModifiedAt.toISOString() : true;
const isAssetTimeUpdated = mtime.toISOString() !== asset.fileModifiedAt.toISOString();
let shouldAssetGoOnline = false;
@ -575,7 +573,7 @@ export class LibraryService extends BaseService {
// Only perform the expensive check if the asset is offline
// TODO: give more feedback on why asset was onlined
shouldAssetGoOnline = await this.checkOfflineAsset(asset, library);
shouldAssetGoOnline = this.checkOfflineAsset(asset, library);
if (shouldAssetGoOnline) {
this.logger.debug(`Asset is back online: ${asset.originalPath}`);
@ -590,7 +588,7 @@ export class LibraryService extends BaseService {
if (isAssetTimeUpdated) {
this.logger.verbose(
`Asset ${asset.originalPath} modification time changed from ${asset.fileModifiedAt?.toISOString()} to ${mtime.toISOString()}, queuing re-import`,
`Asset ${asset.originalPath} modification time changed from ${asset.fileModifiedAt?.toISOString()} to ${mtime.toISOString()}, queuing re-import. Creation time is ${asset.fileCreatedAt?.toISOString()}`,
);
return AssetSyncResult.UPDATE;
@ -626,8 +624,6 @@ export class LibraryService extends BaseService {
return JobStatus.SKIPPED;
}
let assetsOnDiskCount = 0;
const pathsOnDisk = this.storageRepository.walk({
pathsToCrawl: validImportPaths,
includeHidden: false,
@ -654,7 +650,6 @@ export class LibraryService extends BaseService {
ownerId: library.ownerId,
assetPaths: newPaths,
progressCounter: crawlCount,
totalAssets: assetsOnDiskCount,
},
});
this.logger.log(

View file

@ -4,7 +4,7 @@ import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
import { Insertable } from 'kysely';
import _ from 'lodash';
import { Duration } from 'luxon';
import { file } from 'mock-fs/lib/filesystem';
import { Stats } from 'node:fs';
import { constants } from 'node:fs/promises';
import path from 'node:path';
import { SystemConfig } from 'src/config';
@ -163,18 +163,30 @@ export class MetadataService extends BaseService {
this.logger.verbose('Exif Tags', exifTags);
const dates = await this.getDates(asset, exifTags);
const { dateTimeOriginal, localDateTime, timeZone, modifyDate, fileCreatedAt, fileModifiedAt } = this.getDates(
asset,
exifTags,
stats,
);
const { latitude, longitude, country, state, city } = await this.getGeo(exifTags, reverseGeocoding);
const { width, height } = this.getImageDimensions(exifTags);
let fileCreatedAtDate = dateTimeOriginal;
let fileModifiedAtDate = modifyDate;
if (asset.isExternal) {
fileCreatedAtDate = fileCreatedAt;
fileModifiedAtDate = fileModifiedAt;
}
const exifData: Insertable<Exif> = {
assetId: asset.id,
// dates
dateTimeOriginal: dates.dateTimeOriginal,
modifyDate: dates.modifyDate,
timeZone: dates.timeZone,
dateTimeOriginal,
modifyDate,
timeZone,
// gps
latitude,
@ -220,9 +232,9 @@ export class MetadataService extends BaseService {
await this.assetRepository.update({
id: asset.id,
duration: exifTags.Duration?.toString() ?? null,
localDateTime: dates.localDateTime,
fileCreatedAt: exifData.dateTimeOriginal ?? undefined,
fileModifiedAt: exifData.dateTimeOriginal ?? undefined,
localDateTime,
fileCreatedAt: fileCreatedAtDate,
fileModifiedAt: fileModifiedAtDate,
});
await this.assetRepository.upsertJobStatus({
@ -453,7 +465,7 @@ export class MetadataService extends BaseService {
}
} else {
const motionAssetId = this.cryptoRepository.randomUUID();
const dates = await this.getDates(asset, tags);
const dates = this.getDates(asset, tags, stat);
motionAsset = await this.assetRepository.create({
id: motionAssetId,
libraryId: asset.libraryId,
@ -571,7 +583,7 @@ export class MetadataService extends BaseService {
}
}
private async getDates(asset: AssetEntity, exifTags: ImmichTags) {
private getDates(asset: AssetEntity, exifTags: ImmichTags, stat: Stats) {
const dateTime = firstDateTime(exifTags as Maybe<Tags>, EXIF_DATE_TAGS);
this.logger.verbose(`Asset ${asset.id} date time is ${dateTime}`);
@ -592,52 +604,39 @@ export class MetadataService extends BaseService {
let fileCreatedAt = asset.fileCreatedAt;
let fileModifiedAt = asset.fileModifiedAt;
if (!fileCreatedAt || !fileModifiedAt) {
let stat;
if (asset.isExternal) {
// With external assets we need to extract dates from the filesystem, this can't be done with uploades assets as that information is lost on upload
fileCreatedAt = stat.mtime;
fileModifiedAt = stat.mtime;
// Throw error if the file does not exist
stat = await this.storageRepository.stat(asset.originalPath);
if (!fileCreatedAt) {
fileCreatedAt = stat.mtime;
this.logger.debug(
`No valid fileCreatedAt date found for asset ${asset.id}, read file creation date from filesystem: ${fileCreatedAt.toISOString()}`,
);
}
if (!fileModifiedAt) {
fileModifiedAt = stat.mtime;
this.logger.debug(
`No valid fileModifiedAt date found for asset ${asset.id}, read file modification date from filesystem: ${fileModifiedAt.toISOString()}`,
);
}
this.logger.verbose(`External asset ${asset.id} has a file modification time of ${fileCreatedAt.toISOString()}`);
}
let dateTimeOriginal = dateTime?.toDate();
let localDateTime = dateTime?.toDateTime().setZone('UTC', { keepLocalTime: true }).toJSDate();
if (!localDateTime || !dateTimeOriginal) {
this.logger.debug(
`No valid date found in exif tags from asset ${asset.id}, falling back to earliest timestamp between file creation and file modification`,
);
const earliestDate = this.earliestDate(fileModifiedAt, fileCreatedAt);
this.logger.debug(
`No valid date found in exif tags from asset ${asset.id}, falling back to earliest timestamp between file creation and file modification: ${earliestDate.toISOString()}`,
);
dateTimeOriginal = earliestDate;
localDateTime = earliestDate;
}
this.logger.verbose(`Asset ${asset.id} has a local time of ${localDateTime?.toISOString()}`);
this.logger.verbose(`Asset ${asset.id} has a local time of ${localDateTime.toISOString()}`);
let modifyDate = fileModifiedAt;
let modifyDate = asset.fileModifiedAt;
try {
modifyDate = (exifTags.ModifyDate as ExifDateTime)?.toDate() ?? modifyDate;
} catch {}
return {
fileCreatedAt,
fileModifiedAt,
dateTimeOriginal,
timeZone,
localDateTime,
modifyDate,
fileCreatedAt,
fileModifiedAt,
};
}
@ -758,6 +757,8 @@ export class MetadataService extends BaseService {
if (asset.isExternal) {
if (sidecarPath !== asset.sidecarPath) {
this.logger.verbose(`External asset ${asset.id} has sidecar path ${sidecarPath}`);
await this.assetRepository.update({ id: asset.id, sidecarPath });
}
return JobStatus.SUCCESS;

View file

@ -311,11 +311,7 @@ export const sharedLinkResponseStub = {
allowUpload: false,
allowDownload: false,
showMetadata: false,
album: {
...albumResponse,
startDate: assetResponse.fileCreatedAt ?? undefined,
endDate: assetResponse.fileCreatedAt ?? undefined,
},
album: { ...albumResponse, startDate: assetResponse.fileCreatedAt, endDate: assetResponse.fileCreatedAt },
assets: [{ ...assetResponseWithoutMetadata, exifInfo: undefined }],
}),
};

View file

@ -17,7 +17,7 @@
createLibrary,
deleteLibrary,
getAllLibraries,
getAssetCount,
getLibraryStatistics,
getUserAdmin,
scanLibrary,
updateLibrary,
@ -67,7 +67,7 @@
};
const refreshStats = async (listIndex: number) => {
assetCount[listIndex] = await getAssetCount({ id: libraries[listIndex].id });
assetCount[listIndex] = await getLibraryStatistics({ id: libraries[listIndex].id });
owner[listIndex] = await getUserAdmin({ id: libraries[listIndex].ownerId });
};