mirror of
https://github.com/immich-app/immich.git
synced 2026-07-06 12:36:55 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 144ee8e01b |
@@ -82,6 +82,9 @@ url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133"
|
|||||||
version = "7.1.3-6"
|
version = "7.1.3-6"
|
||||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||||
|
|
||||||
|
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||||
|
asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz"
|
||||||
|
|
||||||
[[tools."github:webassembly/binaryen"]]
|
[[tools."github:webassembly/binaryen"]]
|
||||||
version = "version_124"
|
version = "version_124"
|
||||||
backend = "github:webassembly/binaryen"
|
backend = "github:webassembly/binaryen"
|
||||||
|
|||||||
@@ -532,4 +532,5 @@ export const lockableProperties = [
|
|||||||
'rating',
|
'rating',
|
||||||
'timeZone',
|
'timeZone',
|
||||||
'tags',
|
'tags',
|
||||||
] as const;
|
'livePhotoCID',
|
||||||
|
] as const satisfies Array<keyof AssetExifTable>;
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
}
|
}
|
||||||
await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt));
|
await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt));
|
||||||
await this.assetRepository.upsertExif({
|
await this.assetRepository.upsertExif({
|
||||||
exif: { assetId: asset.id, fileSizeInByte: file.size },
|
exif: { assetId: asset.id, fileSizeInByte: file.size, livePhotoCID: dto.livePhotoVideoId },
|
||||||
lockedPropertiesBehavior: 'override',
|
lockedPropertiesBehavior: 'override',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -396,6 +396,23 @@ export class MetadataService extends BaseService {
|
|||||||
tasks.push(() => this.applyMotionPhotos(asset, exifTags, dates, stats));
|
tasks.push(() => this.applyMotionPhotos(asset, exifTags, dates, stats));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.isMotionPhoto(asset, exifTags) && !exifData.livePhotoCID && asset.livePhotoVideoId) {
|
||||||
|
// delete the motion part if the asset gets changed to not be a live photo anymore
|
||||||
|
tasks.push(async () => {
|
||||||
|
if (!asset.livePhotoVideoId) {
|
||||||
|
throw new Error('asset.livePhotoVideoId should not have been reset');
|
||||||
|
}
|
||||||
|
await this.assetRepository.update({ id: asset.id, livePhotoVideoId: null });
|
||||||
|
const count = await this.assetRepository.getLivePhotoCount(asset.livePhotoVideoId);
|
||||||
|
if (count === 0) {
|
||||||
|
await this.jobRepository.queue({
|
||||||
|
name: JobName.AssetDelete,
|
||||||
|
data: { id: asset.livePhotoVideoId, deleteOnDisk: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (isFaceImportEnabled(metadata) && this.hasTaggedFaces(exifTags)) {
|
if (isFaceImportEnabled(metadata) && this.hasTaggedFaces(exifTags)) {
|
||||||
tasks.push(() => this.applyTaggedFaces(asset, exifTags));
|
tasks.push(() => this.applyTaggedFaces(asset, exifTags));
|
||||||
}
|
}
|
||||||
@@ -500,7 +517,7 @@ 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, timeZone } = _.pick(
|
const { description, dateTimeOriginal, latitude, longitude, rating, tags, timeZone, livePhotoCID } = _.pick(
|
||||||
{
|
{
|
||||||
description: asset.exifInfo.description,
|
description: asset.exifInfo.description,
|
||||||
dateTimeOriginal: asset.exifInfo.dateTimeOriginal,
|
dateTimeOriginal: asset.exifInfo.dateTimeOriginal,
|
||||||
@@ -509,6 +526,7 @@ export class MetadataService extends BaseService {
|
|||||||
rating: asset.exifInfo.rating ?? 0,
|
rating: asset.exifInfo.rating ?? 0,
|
||||||
tags: asset.exifInfo.tags,
|
tags: asset.exifInfo.tags,
|
||||||
timeZone: asset.exifInfo.timeZone,
|
timeZone: asset.exifInfo.timeZone,
|
||||||
|
livePhotoCID: asset.exifInfo.livePhotoCID,
|
||||||
},
|
},
|
||||||
lockedProperties,
|
lockedProperties,
|
||||||
);
|
);
|
||||||
@@ -522,6 +540,7 @@ export class MetadataService extends BaseService {
|
|||||||
GPSLongitude: longitude,
|
GPSLongitude: longitude,
|
||||||
Rating: rating,
|
Rating: rating,
|
||||||
TagsList: tags,
|
TagsList: tags,
|
||||||
|
ContentIdentifier: livePhotoCID,
|
||||||
},
|
},
|
||||||
_.isUndefined,
|
_.isUndefined,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { Stats } from 'node:fs';
|
|||||||
import { writeFile } from 'node:fs/promises';
|
import { writeFile } from 'node:fs/promises';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
import { AssetType, JobName } from 'src/enum';
|
||||||
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
||||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
import { EventRepository } from 'src/repositories/event.repository';
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
|
import { JobRepository } from 'src/repositories/job.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { MetadataRepository } from 'src/repositories/metadata.repository';
|
import { MetadataRepository } from 'src/repositories/metadata.repository';
|
||||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||||
@@ -41,7 +43,7 @@ const setup = (db?: Kysely<DB>) => {
|
|||||||
SystemMetadataRepository,
|
SystemMetadataRepository,
|
||||||
TagRepository,
|
TagRepository,
|
||||||
],
|
],
|
||||||
mock: [EventRepository, StorageRepository, LoggingRepository],
|
mock: [EventRepository, JobRepository, StorageRepository, LoggingRepository],
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.getMock(StorageRepository).stat.mockResolvedValue({
|
ctx.getMock(StorageRepository).stat.mockResolvedValue({
|
||||||
@@ -152,4 +154,24 @@ describe(MetadataService.name, () => {
|
|||||||
).resolves.toEqual({ dateTimeOriginal: new Date('4260-03-05T04:04:12.000Z') });
|
).resolves.toEqual({ dateTimeOriginal: new Date('4260-03-05T04:04:12.000Z') });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should remove motion asset if asset is updated to not be a motion photo anymore', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||||
|
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const { asset: motionAsset } = await ctx.newAsset({ ownerId: user.id, type: AssetType.Video });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id, livePhotoVideoId: motionAsset.id });
|
||||||
|
await ctx.newExif({ assetId: asset.id, description: '' });
|
||||||
|
|
||||||
|
await sut.handleMetadataExtraction({ id: asset.id });
|
||||||
|
|
||||||
|
await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toEqual(
|
||||||
|
expect.objectContaining({ livePhotoVideoId: null }),
|
||||||
|
);
|
||||||
|
expect(ctx.getMock(JobRepository).queue).toHaveBeenCalledWith({
|
||||||
|
name: JobName.AssetDelete,
|
||||||
|
data: { id: motionAsset.id, deleteOnDisk: true },
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user