Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04ce6b3bcb | |||
| fc921fbe9f | |||
| f588a609d9 | |||
| 1496627551 |
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
@@ -24,6 +25,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
useAppSettingsState(AppSettingsEnum.syncAlbums);
|
||||
final isDarkTheme = context.isDarkTheme;
|
||||
final albums = ref.watch(backupProvider).availableAlbums;
|
||||
final searchQuery = useState('');
|
||||
final formFocus = useFocusNode();
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
@@ -33,6 +36,10 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
final filteredAlbums = albums.where((album) {
|
||||
return album.name.toLowerCase().contains(searchQuery.value.toLowerCase());
|
||||
}).toList();
|
||||
|
||||
buildAlbumSelectionList() {
|
||||
if (albums.isEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
@@ -48,10 +55,10 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
((context, index) {
|
||||
return AlbumInfoListTile(
|
||||
album: albums[index],
|
||||
album: filteredAlbums[index],
|
||||
);
|
||||
}),
|
||||
childCount: albums.length,
|
||||
childCount: filteredAlbums.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -74,10 +81,10 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
),
|
||||
itemCount: albums.length,
|
||||
itemCount: filteredAlbums.length,
|
||||
itemBuilder: ((context, index) {
|
||||
return AlbumInfoCard(
|
||||
album: albums[index],
|
||||
album: filteredAlbums[index],
|
||||
);
|
||||
}),
|
||||
),
|
||||
@@ -247,8 +254,9 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
elevation: 5,
|
||||
title: Text(
|
||||
@@ -277,11 +285,56 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// buildSearchBar(),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 4),
|
||||
child: TextField(
|
||||
focusNode: formFocus,
|
||||
onChanged: (value) => searchQuery.value = value,
|
||||
onTapOutside: (_) => formFocus.unfocus(),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.only(left: 24),
|
||||
filled: true,
|
||||
fillColor: context.primaryColor.withValues(alpha: 0.1),
|
||||
hintStyle: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.themeData.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(25)),
|
||||
borderSide: BorderSide(
|
||||
color: context.colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(25)),
|
||||
borderSide: BorderSide(
|
||||
color: context.colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
),
|
||||
disabledBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(25)),
|
||||
borderSide: BorderSide(
|
||||
color: context.colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(25)),
|
||||
borderSide: BorderSide(
|
||||
color: context.colorScheme.primary.withAlpha(150),
|
||||
),
|
||||
),
|
||||
prefixIcon: Icon(
|
||||
Icons.search_rounded,
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
hintText: 'search_albums'.tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverLayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.crossAxisExtent > 600) {
|
||||
|
||||
Vendored
-1
@@ -143,7 +143,6 @@ export interface Assets {
|
||||
isFavorite: Generated<boolean>;
|
||||
isOffline: Generated<boolean>;
|
||||
isVisible: Generated<boolean>;
|
||||
isDirty: Generated<boolean>;
|
||||
libraryId: string | null;
|
||||
livePhotoVideoId: string | null;
|
||||
localDateTime: Timestamp | null;
|
||||
|
||||
@@ -125,9 +125,6 @@ export class AssetEntity {
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isOffline!: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isDirty!: boolean;
|
||||
|
||||
@Column({ type: 'bytea' })
|
||||
@Index()
|
||||
checksum!: Buffer; // sha1 checksum
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddAssetIsDirty1742127949957 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "isDirty" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "isDirty"`);
|
||||
}
|
||||
}
|
||||
@@ -264,7 +264,7 @@ describe(AssetService.name, () => {
|
||||
|
||||
await sut.update(authStub.admin, 'asset-1', { isFavorite: true });
|
||||
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true, isDirty: true });
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
|
||||
});
|
||||
|
||||
it('should update the exif description', async () => {
|
||||
@@ -371,7 +371,6 @@ describe(AssetService.name, () => {
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoStillAsset.id,
|
||||
livePhotoVideoId: assetStub.livePhotoMotionAsset.id,
|
||||
isDirty: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -393,7 +392,6 @@ describe(AssetService.name, () => {
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.livePhotoStillAsset.id,
|
||||
livePhotoVideoId: null,
|
||||
isDirty: true,
|
||||
});
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: true });
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('asset.show', {
|
||||
@@ -431,7 +429,7 @@ describe(AssetService.name, () => {
|
||||
|
||||
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true });
|
||||
|
||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true, isDirty: true });
|
||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
|
||||
});
|
||||
|
||||
it('should not update Assets table if no relevant fields are provided', async () => {
|
||||
|
||||
@@ -117,7 +117,7 @@ export class AssetService extends BaseService {
|
||||
|
||||
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude, rating });
|
||||
|
||||
const asset = await this.assetRepository.update({ id, isDirty: true, ...rest });
|
||||
const asset = await this.assetRepository.update({ id, ...rest });
|
||||
|
||||
if (previousMotion) {
|
||||
await onAfterUnlink(repos, { userId: auth.user.id, livePhotoVideoId: previousMotion.id });
|
||||
@@ -144,7 +144,7 @@ export class AssetService extends BaseService {
|
||||
options.duplicateId != undefined ||
|
||||
options.rating != undefined
|
||||
) {
|
||||
await this.assetRepository.updateAll(ids, { isDirty: true, ...options });
|
||||
await this.assetRepository.updateAll(ids, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ describe(MetadataService.name, () => {
|
||||
it('should handle an asset that could not be found', async () => {
|
||||
await expect(sut.handleMetadataExtraction({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -125,11 +125,11 @@ describe(MetadataService.name, () => {
|
||||
it('should handle a date in a sidecar file', async () => {
|
||||
const originalDate = new Date('2023-11-21T16:13:17.517Z');
|
||||
const sidecarDate = new Date('2022-01-01T00:00:00.000Z');
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecar);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
mockReadTags({ CreationDate: originalDate.toISOString() }, { CreationDate: sidecarDate.toISOString() });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.sidecar.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.sidecar.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ dateTimeOriginal: sidecarDate }));
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -144,14 +144,14 @@ describe(MetadataService.name, () => {
|
||||
it('should take the file modification date when missing exif and earlier than creation date', async () => {
|
||||
const fileCreatedAt = new Date('2022-01-01T00:00:00.000Z');
|
||||
const fileModifiedAt = new Date('2021-01-01T00:00:00.000Z');
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({
|
||||
FileCreateDate: fileCreatedAt.toISOString(),
|
||||
FileModifyDate: fileModifiedAt.toISOString(),
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ dateTimeOriginal: fileModifiedAt }),
|
||||
);
|
||||
@@ -167,14 +167,14 @@ describe(MetadataService.name, () => {
|
||||
it('should take the file creation date when missing exif and earlier than modification date', async () => {
|
||||
const fileCreatedAt = new Date('2021-01-01T00:00:00.000Z');
|
||||
const fileModifiedAt = new Date('2022-01-01T00:00:00.000Z');
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({
|
||||
FileCreateDate: fileCreatedAt.toISOString(),
|
||||
FileModifyDate: fileModifiedAt.toISOString(),
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ dateTimeOriginal: fileCreatedAt }));
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.image.id,
|
||||
@@ -187,7 +187,7 @@ describe(MetadataService.name, () => {
|
||||
|
||||
it('should account for the server being in a non-UTC timezone', async () => {
|
||||
process.env.TZ = 'America/Los_Angeles';
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecar);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
mockReadTags({ DateTimeOriginal: '2022:01:01 00:00:00' });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -205,7 +205,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should handle lists of numbers', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({
|
||||
ISO: [160],
|
||||
FileCreateDate: assetStub.image.fileCreatedAt.toISOString(),
|
||||
@@ -213,7 +213,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ iso: 160 }));
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: assetStub.image.id,
|
||||
@@ -225,7 +225,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should apply reverse geocoding', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.withLocation);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.withLocation]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ reverseGeocoding: { enabled: true } });
|
||||
mocks.map.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' });
|
||||
mockReadTags({
|
||||
@@ -236,7 +236,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ city: 'City', state: 'State', country: 'Country' }),
|
||||
);
|
||||
@@ -250,19 +250,19 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should discard latitude and longitude on null island', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.withLocation);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.withLocation]);
|
||||
mockReadTags({
|
||||
GPSLatitude: 0,
|
||||
GPSLongitude: 0,
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ latitude: null, longitude: null }));
|
||||
});
|
||||
|
||||
it('should extract tags from TagsList', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ TagsList: ['Parent'] });
|
||||
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
|
||||
|
||||
@@ -272,7 +272,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract hierarchy from TagsList', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ TagsList: ['Parent/Child'] });
|
||||
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
|
||||
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert);
|
||||
@@ -292,7 +292,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract tags from Keywords as a string', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Keywords: 'Parent' });
|
||||
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
|
||||
|
||||
@@ -302,7 +302,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract tags from Keywords as a list', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Keywords: ['Parent'] });
|
||||
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
|
||||
|
||||
@@ -312,7 +312,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract tags from Keywords as a list with a number', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Keywords: ['Parent', 2024] });
|
||||
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
|
||||
|
||||
@@ -323,7 +323,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract hierarchal tags from Keywords', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Keywords: 'Parent/Child' });
|
||||
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
|
||||
|
||||
@@ -342,7 +342,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should ignore Keywords when TagsList is present', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Keywords: 'Child', TagsList: ['Parent/Child'] });
|
||||
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
|
||||
|
||||
@@ -361,7 +361,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract hierarchy from HierarchicalSubject', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ HierarchicalSubject: ['Parent|Child', 'TagA'] });
|
||||
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
|
||||
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert);
|
||||
@@ -382,7 +382,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract tags from HierarchicalSubject as a list with a number', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ HierarchicalSubject: ['Parent', 2024] });
|
||||
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
|
||||
|
||||
@@ -393,7 +393,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract ignore / characters in a HierarchicalSubject tag', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ HierarchicalSubject: ['Mom/Dad'] });
|
||||
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
|
||||
|
||||
@@ -407,7 +407,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should ignore HierarchicalSubject when TagsList is present', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ HierarchicalSubject: ['Parent2|Child2'], TagsList: ['Parent/Child'] });
|
||||
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
|
||||
|
||||
@@ -426,7 +426,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should remove existing tags', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -435,11 +435,11 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should not apply motion photos if asset is video', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.livePhotoMotionAsset, isVisible: true });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoMotionAsset, isVisible: true }]);
|
||||
mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.livePhotoMotionAsset.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id, {
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id], {
|
||||
faces: { person: false },
|
||||
});
|
||||
expect(mocks.storage.createOrOverwriteFile).not.toHaveBeenCalled();
|
||||
@@ -451,7 +451,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should handle an invalid Directory Item', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({
|
||||
MotionPhoto: 1,
|
||||
ContainerDirectory: [{ Foo: 100 }],
|
||||
@@ -461,20 +461,20 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract the correct video orientation', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.video);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.video]);
|
||||
mocks.media.probe.mockResolvedValue(probeStub.videoStreamVertical2160p);
|
||||
mockReadTags({});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.video.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.video.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ orientation: ExifOrientation.Rotate270CW.toString() }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
@@ -497,7 +497,7 @@ describe(MetadataService.name, () => {
|
||||
assetStub.livePhotoWithOriginalFileName.originalPath,
|
||||
'MotionPhotoVideo',
|
||||
);
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.livePhotoWithOriginalFileName.id, {
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoWithOriginalFileName.id], {
|
||||
faces: { person: false },
|
||||
});
|
||||
expect(mocks.asset.create).toHaveBeenCalledWith({
|
||||
@@ -525,7 +525,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract the EmbeddedVideo tag from Samsung JPEG motion photos', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
EmbeddedVideoFile: new BinaryField(0, ''),
|
||||
@@ -545,7 +545,7 @@ describe(MetadataService.name, () => {
|
||||
assetStub.livePhotoWithOriginalFileName.originalPath,
|
||||
'EmbeddedVideoFile',
|
||||
);
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.livePhotoWithOriginalFileName.id, {
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoWithOriginalFileName.id], {
|
||||
faces: { person: false },
|
||||
});
|
||||
expect(mocks.asset.create).toHaveBeenCalledWith({
|
||||
@@ -573,7 +573,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract the motion photo video from the XMP directory entry ', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
@@ -589,7 +589,7 @@ describe(MetadataService.name, () => {
|
||||
mocks.storage.readFile.mockResolvedValue(video);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.livePhotoWithOriginalFileName.id, {
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoWithOriginalFileName.id], {
|
||||
faces: { person: false },
|
||||
});
|
||||
expect(mocks.storage.readFile).toHaveBeenCalledWith(
|
||||
@@ -621,7 +621,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should delete old motion photo video assets if they do not match what is extracted', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoWithOriginalFileName);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.livePhotoWithOriginalFileName]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
@@ -647,7 +647,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should not create a new motion photo video asset if the hash of the extracted video matches an existing asset', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoStillAsset);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.livePhotoStillAsset]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
@@ -669,7 +669,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should link and hide motion video asset to still asset if the hash of the extracted video matches an existing asset', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
@@ -694,11 +694,9 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should not update storage usage if motion photo is external', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({
|
||||
...assetStub.livePhotoStillAsset,
|
||||
livePhotoVideoId: null,
|
||||
isExternal: true,
|
||||
});
|
||||
mocks.asset.getByIds.mockResolvedValue([
|
||||
{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null, isExternal: true },
|
||||
]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
@@ -740,11 +738,11 @@ describe(MetadataService.name, () => {
|
||||
tz: 'UTC-11:30',
|
||||
Rating: 3,
|
||||
};
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags(tags);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith({
|
||||
assetId: assetStub.image.id,
|
||||
bitsPerSample: expect.any(Number),
|
||||
@@ -800,11 +798,11 @@ describe(MetadataService.name, () => {
|
||||
DateTimeOriginal: ExifDateTime.fromISO(someDate + '+00:00'),
|
||||
tz: undefined,
|
||||
};
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags(tags);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
timeZone: 'UTC+0',
|
||||
@@ -813,7 +811,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract duration', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.video });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.video }]);
|
||||
mocks.media.probe.mockResolvedValue({
|
||||
...probeStub.videoStreamH264,
|
||||
format: {
|
||||
@@ -824,7 +822,7 @@ describe(MetadataService.name, () => {
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.video.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.video.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalled();
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -835,7 +833,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should only extract duration for videos', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.image });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.image }]);
|
||||
mocks.media.probe.mockResolvedValue({
|
||||
...probeStub.videoStreamH264,
|
||||
format: {
|
||||
@@ -845,7 +843,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalled();
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -856,7 +854,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should omit duration of zero', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.video });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.video }]);
|
||||
mocks.media.probe.mockResolvedValue({
|
||||
...probeStub.videoStreamH264,
|
||||
format: {
|
||||
@@ -867,7 +865,7 @@ describe(MetadataService.name, () => {
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.video.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.video.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalled();
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -878,7 +876,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should a handle duration of 1 week', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({ ...assetStub.video });
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.video }]);
|
||||
mocks.media.probe.mockResolvedValue({
|
||||
...probeStub.videoStreamH264,
|
||||
format: {
|
||||
@@ -889,7 +887,7 @@ describe(MetadataService.name, () => {
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.video.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.video.id], { faces: { person: false } });
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalled();
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -900,7 +898,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should ignore duration from exif data', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({}, { Duration: { Value: 123 } });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -908,7 +906,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should trim whitespace from description', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Description: '\t \v \f \n \r' });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -928,7 +926,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should handle a numeric description', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Description: 1000 });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -940,7 +938,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should skip importing metadata when the feature is disabled', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: false } } });
|
||||
mockReadTags(metadataStub.withFace);
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -948,7 +946,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should skip importing metadata face for assets without tags.RegionInfo', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
|
||||
mockReadTags(metadataStub.empty);
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -956,7 +954,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should skip importing faces without name', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
|
||||
mockReadTags(metadataStub.withFaceNoName);
|
||||
mocks.person.getDistinctNames.mockResolvedValue([]);
|
||||
@@ -968,7 +966,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should skip importing faces with empty name', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
|
||||
mockReadTags(metadataStub.withFaceEmptyName);
|
||||
mocks.person.getDistinctNames.mockResolvedValue([]);
|
||||
@@ -980,14 +978,14 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should apply metadata face tags creating new persons', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
|
||||
mockReadTags(metadataStub.withFace);
|
||||
mocks.person.getDistinctNames.mockResolvedValue([]);
|
||||
mocks.person.createAll.mockResolvedValue([personStub.withName.id]);
|
||||
mocks.person.update.mockResolvedValue(personStub.withName);
|
||||
await sut.handleMetadataExtraction({ id: assetStub.primaryImage.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.primaryImage.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.primaryImage.id], { faces: { person: false } });
|
||||
expect(mocks.person.getDistinctNames).toHaveBeenCalledWith(assetStub.primaryImage.ownerId, { withHidden: true });
|
||||
expect(mocks.person.createAll).toHaveBeenCalledWith([
|
||||
expect.objectContaining({ name: personStub.withName.name }),
|
||||
@@ -1021,14 +1019,14 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should assign metadata face tags to existing persons', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
|
||||
mockReadTags(metadataStub.withFace);
|
||||
mocks.person.getDistinctNames.mockResolvedValue([{ id: personStub.withName.id, name: personStub.withName.name }]);
|
||||
mocks.person.createAll.mockResolvedValue([]);
|
||||
mocks.person.update.mockResolvedValue(personStub.withName);
|
||||
await sut.handleMetadataExtraction({ id: assetStub.primaryImage.id });
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.primaryImage.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.primaryImage.id], { faces: { person: false } });
|
||||
expect(mocks.person.getDistinctNames).toHaveBeenCalledWith(assetStub.primaryImage.ownerId, { withHidden: true });
|
||||
expect(mocks.person.createAll).not.toHaveBeenCalled();
|
||||
expect(mocks.person.refreshFaces).toHaveBeenCalledWith(
|
||||
@@ -1053,7 +1051,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should handle invalid modify date', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ ModifyDate: '00:00:00.000' });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -1065,7 +1063,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should handle invalid rating value', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Rating: 6 });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -1077,7 +1075,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should handle valid rating value', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Rating: 5 });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -1089,7 +1087,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should handle valid negative rating value', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({ Rating: -1 });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -1101,11 +1099,11 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should handle livePhotoCID not set', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
|
||||
await expect(sut.handleMetadataExtraction({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.image.id, { faces: { person: false } });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
expect(mocks.asset.findLivePhotoMatch).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.update).not.toHaveBeenCalledWith(expect.objectContaining({ isVisible: false }));
|
||||
expect(mocks.album.removeAsset).not.toHaveBeenCalled();
|
||||
@@ -1113,14 +1111,14 @@ describe(MetadataService.name, () => {
|
||||
|
||||
it('should handle not finding a match', async () => {
|
||||
mocks.media.probe.mockResolvedValue(probeStub.videoStreamVertical2160p);
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]);
|
||||
mockReadTags({ ContentIdentifier: 'CID' });
|
||||
|
||||
await expect(sut.handleMetadataExtraction({ id: assetStub.livePhotoMotionAsset.id })).resolves.toBe(
|
||||
JobStatus.SUCCESS,
|
||||
);
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id, {
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id], {
|
||||
faces: { person: false },
|
||||
});
|
||||
expect(mocks.asset.findLivePhotoMatch).toHaveBeenCalledWith({
|
||||
@@ -1134,7 +1132,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should link photo and video', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoStillAsset);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.livePhotoStillAsset]);
|
||||
mocks.asset.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
mockReadTags({ ContentIdentifier: 'CID' });
|
||||
|
||||
@@ -1142,7 +1140,7 @@ describe(MetadataService.name, () => {
|
||||
JobStatus.SUCCESS,
|
||||
);
|
||||
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(assetStub.livePhotoStillAsset.id, {
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id], {
|
||||
faces: { person: false },
|
||||
});
|
||||
expect(mocks.asset.findLivePhotoMatch).toHaveBeenCalledWith({
|
||||
@@ -1160,10 +1158,12 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should notify clients on live photo link', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({
|
||||
...assetStub.livePhotoStillAsset,
|
||||
exifInfo: { livePhotoCID: assetStub.livePhotoMotionAsset.id } as ExifEntity,
|
||||
});
|
||||
mocks.asset.getByIds.mockResolvedValue([
|
||||
{
|
||||
...assetStub.livePhotoStillAsset,
|
||||
exifInfo: { livePhotoCID: assetStub.livePhotoMotionAsset.id } as ExifEntity,
|
||||
},
|
||||
]);
|
||||
mocks.asset.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
mockReadTags({ ContentIdentifier: 'CID' });
|
||||
|
||||
@@ -1178,10 +1178,12 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should search by libraryId', async () => {
|
||||
mocks.asset.getById.mockResolvedValue({
|
||||
...assetStub.livePhotoStillAsset,
|
||||
libraryId: 'library-id',
|
||||
});
|
||||
mocks.asset.getByIds.mockResolvedValue([
|
||||
{
|
||||
...assetStub.livePhotoStillAsset,
|
||||
libraryId: 'library-id',
|
||||
},
|
||||
]);
|
||||
mockReadTags({ ContentIdentifier: 'CID' });
|
||||
|
||||
await expect(sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id })).resolves.toBe(
|
||||
@@ -1202,7 +1204,7 @@ describe(MetadataService.name, () => {
|
||||
{ Device: { Manufacturer: '1', ModelName: '2' }, AndroidMake: '3', AndroidModel: '4' },
|
||||
{ AndroidMake: '1', AndroidModel: '2' },
|
||||
])('should read camera make and model correct place %s', async (metaData) => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags(metaData);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -1249,19 +1251,19 @@ describe(MetadataService.name, () => {
|
||||
|
||||
describe('handleSidecarSync', () => {
|
||||
it('should do nothing if asset could not be found', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(void 0);
|
||||
mocks.asset.getByIds.mockResolvedValue([]);
|
||||
await expect(sut.handleSidecarSync({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
|
||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing if asset has no sidecar path', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
await expect(sut.handleSidecarSync({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
|
||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set sidecar path if exists (sidecar named photo.ext.xmp)', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecar);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
mocks.storage.checkFileExists.mockResolvedValue(true);
|
||||
|
||||
await expect(sut.handleSidecarSync({ id: assetStub.sidecar.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
@@ -1276,7 +1278,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should set sidecar path if exists (sidecar named photo.xmp)', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecarWithoutExt);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecarWithoutExt]);
|
||||
mocks.storage.checkFileExists.mockResolvedValueOnce(false);
|
||||
mocks.storage.checkFileExists.mockResolvedValueOnce(true);
|
||||
|
||||
@@ -1293,7 +1295,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should set sidecar path if exists (two sidecars named photo.ext.xmp and photo.xmp, should pick photo.ext.xmp)', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecar);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
mocks.storage.checkFileExists.mockResolvedValueOnce(true);
|
||||
mocks.storage.checkFileExists.mockResolvedValueOnce(true);
|
||||
|
||||
@@ -1311,7 +1313,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should unset sidecar path if file does not exist anymore', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecar);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
mocks.storage.checkFileExists.mockResolvedValue(false);
|
||||
|
||||
await expect(sut.handleSidecarSync({ id: assetStub.sidecar.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
@@ -1328,26 +1330,26 @@ describe(MetadataService.name, () => {
|
||||
|
||||
describe('handleSidecarDiscovery', () => {
|
||||
it('should skip hidden assets', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]);
|
||||
await sut.handleSidecarDiscovery({ id: assetStub.livePhotoMotionAsset.id });
|
||||
expect(mocks.storage.checkFileExists).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip assets with a sidecar path', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecar);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
await sut.handleSidecarDiscovery({ id: assetStub.sidecar.id });
|
||||
expect(mocks.storage.checkFileExists).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing when a sidecar is not found ', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mocks.storage.checkFileExists.mockResolvedValue(false);
|
||||
await sut.handleSidecarDiscovery({ id: assetStub.image.id });
|
||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update a image asset when a sidecar is found', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mocks.storage.checkFileExists.mockResolvedValue(true);
|
||||
await sut.handleSidecarDiscovery({ id: assetStub.image.id });
|
||||
expect(mocks.storage.checkFileExists).toHaveBeenCalledWith('/original/path.jpg.xmp', constants.R_OK);
|
||||
@@ -1358,7 +1360,7 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should update a video asset when a sidecar is found', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.video);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.video]);
|
||||
mocks.storage.checkFileExists.mockResolvedValue(true);
|
||||
await sut.handleSidecarDiscovery({ id: assetStub.video.id });
|
||||
expect(mocks.storage.checkFileExists).toHaveBeenCalledWith('/original/path.ext.xmp', constants.R_OK);
|
||||
@@ -1371,13 +1373,13 @@ describe(MetadataService.name, () => {
|
||||
|
||||
describe('handleSidecarWrite', () => {
|
||||
it('should skip assets that do not exist anymore', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(void 0);
|
||||
mocks.asset.getByIds.mockResolvedValue([]);
|
||||
await expect(sut.handleSidecarWrite({ id: 'asset-123' })).resolves.toBe(JobStatus.FAILED);
|
||||
expect(mocks.metadata.writeTags).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip jobs with not metadata', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecar);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
await expect(sut.handleSidecarWrite({ id: assetStub.sidecar.id })).resolves.toBe(JobStatus.SKIPPED);
|
||||
expect(mocks.metadata.writeTags).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -1387,7 +1389,7 @@ describe(MetadataService.name, () => {
|
||||
const gps = 12;
|
||||
const date = '2023-11-22T04:56:12.196Z';
|
||||
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.sidecar);
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
await expect(
|
||||
sut.handleSidecarWrite({
|
||||
id: assetStub.sidecar.id,
|
||||
|
||||
@@ -29,7 +29,6 @@ import { ReverseGeocodeResult } from 'src/repositories/map.repository';
|
||||
import { ImmichTags } from 'src/repositories/metadata.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { JobOf } from 'src/types';
|
||||
import { getSidecarPath } from 'src/utils/asset.util';
|
||||
import { isFaceImportEnabled } from 'src/utils/misc';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
import { upsertTags } from 'src/utils/tag';
|
||||
@@ -163,29 +162,15 @@ export class MetadataService extends BaseService {
|
||||
|
||||
@OnJob({ name: JobName.METADATA_EXTRACTION, queue: QueueName.METADATA_EXTRACTION })
|
||||
async handleMetadataExtraction(data: JobOf<JobName.METADATA_EXTRACTION>): Promise<JobStatus> {
|
||||
const [{ metadata, reverseGeocoding }, asset] = await Promise.all([
|
||||
const [{ metadata, reverseGeocoding }, [asset]] = await Promise.all([
|
||||
this.getConfig({ withCache: true }),
|
||||
this.assetRepository.getById(data.id, { faces: { person: false } }),
|
||||
this.assetRepository.getByIds([data.id], { faces: { person: false } }),
|
||||
]);
|
||||
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
if (asset.isDirty) {
|
||||
const { exifInfo } = (await this.assetRepository.getById(asset.id, { exifInfo: true })) || {};
|
||||
await this.handleSidecarWrite({
|
||||
id: asset.id,
|
||||
description: exifInfo?.description,
|
||||
dateTimeOriginal: exifInfo?.dateTimeOriginal?.toISOString(),
|
||||
latitude: exifInfo?.latitude ?? undefined,
|
||||
longitude: exifInfo?.longitude ?? undefined,
|
||||
rating: exifInfo?.rating ?? undefined,
|
||||
tags: true,
|
||||
});
|
||||
asset.sidecarPath = asset.sidecarPath || getSidecarPath(asset);
|
||||
}
|
||||
|
||||
const exifTags = await this.getExifTags(asset);
|
||||
if (!exifTags.FileCreateDate || !exifTags.FileModifyDate || exifTags.FileSize === undefined) {
|
||||
this.logger.warn(`Missing file creation or modification date for asset ${asset.id}: ${asset.originalPath}`);
|
||||
@@ -329,14 +314,14 @@ export class MetadataService extends BaseService {
|
||||
@OnJob({ name: JobName.SIDECAR_WRITE, queue: QueueName.SIDECAR })
|
||||
async handleSidecarWrite(job: JobOf<JobName.SIDECAR_WRITE>): Promise<JobStatus> {
|
||||
const { id, description, dateTimeOriginal, latitude, longitude, rating, tags } = job;
|
||||
const asset = await this.assetRepository.getById(id, { tags: true });
|
||||
const [asset] = await this.assetRepository.getByIds([id], { tags: true });
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const tagsList = (asset.tags || []).map((tag) => tag.value);
|
||||
|
||||
const sidecarPath = asset.sidecarPath || getSidecarPath(asset);
|
||||
const sidecarPath = asset.sidecarPath || `${asset.originalPath}.xmp`;
|
||||
const exif = _.omitBy(
|
||||
<Tags>{
|
||||
Description: description,
|
||||
@@ -357,7 +342,7 @@ export class MetadataService extends BaseService {
|
||||
await this.metadataRepository.writeTags(sidecarPath, exif);
|
||||
|
||||
if (!asset.sidecarPath) {
|
||||
await this.assetRepository.update({ id, sidecarPath, isDirty: false });
|
||||
await this.assetRepository.update({ id, sidecarPath });
|
||||
}
|
||||
|
||||
return JobStatus.SUCCESS;
|
||||
@@ -769,7 +754,7 @@ export class MetadataService extends BaseService {
|
||||
}
|
||||
|
||||
private async processSidecar(id: string, isSync: boolean): Promise<JobStatus> {
|
||||
const asset = await this.assetRepository.getById(id);
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
|
||||
@@ -90,7 +90,6 @@ export class TagService extends BaseService {
|
||||
|
||||
const results = await this.tagRepository.upsertAssetIds(items);
|
||||
for (const assetId of new Set(results.map((item) => item.assetsId))) {
|
||||
await this.assetRepository.update({ id: assetId, isDirty: true });
|
||||
await this.eventRepository.emit('asset.tag', { assetId });
|
||||
}
|
||||
|
||||
@@ -108,7 +107,6 @@ export class TagService extends BaseService {
|
||||
|
||||
for (const { id: assetId, success } of results) {
|
||||
if (success) {
|
||||
await this.assetRepository.update({ id: assetId, isDirty: true });
|
||||
await this.eventRepository.emit('asset.tag', { assetId });
|
||||
}
|
||||
}
|
||||
@@ -127,7 +125,6 @@ export class TagService extends BaseService {
|
||||
|
||||
for (const { id: assetId, success } of results) {
|
||||
if (success) {
|
||||
await this.assetRepository.update({ id: assetId, isDirty: true });
|
||||
await this.eventRepository.emit('asset.untag', { assetId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.respons
|
||||
import { UploadFieldName } from 'src/dtos/asset-media.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AssetFileType, AssetType, Permission } from 'src/enum';
|
||||
import { AuthRequest } from 'src/middleware/auth.guard';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
@@ -18,8 +17,6 @@ const getFileByType = (files: AssetFileEntity[] | undefined, type: AssetFileType
|
||||
return (files || []).find((file) => file.type === type);
|
||||
};
|
||||
|
||||
export const getSidecarPath = (asset: AssetEntity) => `${asset.originalPath}.xmp`;
|
||||
|
||||
export const getAssetFiles = (files?: AssetFileEntity[]) => ({
|
||||
previewFile: getFileByType(files, AssetFileType.PREVIEW),
|
||||
thumbnailFile: getFileByType(files, AssetFileType.THUMBNAIL),
|
||||
|
||||
Vendored
-20
@@ -88,7 +88,6 @@ export const assetStub = {
|
||||
isExternal: false,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
noWebpPath: Object.freeze<AssetEntity>({
|
||||
@@ -127,7 +126,6 @@ export const assetStub = {
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
noThumbhash: Object.freeze<AssetEntity>({
|
||||
@@ -163,7 +161,6 @@ export const assetStub = {
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
primaryImage: Object.freeze<AssetEntity>({
|
||||
@@ -210,7 +207,6 @@ export const assetStub = {
|
||||
]),
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
image: Object.freeze<AssetEntity>({
|
||||
@@ -251,7 +247,6 @@ export const assetStub = {
|
||||
} as ExifEntity,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
trashed: Object.freeze<AssetEntity>({
|
||||
@@ -292,7 +287,6 @@ export const assetStub = {
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
status: AssetStatus.TRASHED,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
trashedOffline: Object.freeze<AssetEntity>({
|
||||
@@ -334,7 +328,6 @@ export const assetStub = {
|
||||
} as ExifEntity,
|
||||
duplicateId: null,
|
||||
isOffline: true,
|
||||
isDirty: false,
|
||||
}),
|
||||
archived: Object.freeze<AssetEntity>({
|
||||
id: 'asset-id',
|
||||
@@ -374,7 +367,6 @@ export const assetStub = {
|
||||
} as ExifEntity,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
external: Object.freeze<AssetEntity>({
|
||||
@@ -414,7 +406,6 @@ export const assetStub = {
|
||||
} as ExifEntity,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
image1: Object.freeze<AssetEntity>({
|
||||
@@ -453,7 +444,6 @@ export const assetStub = {
|
||||
} as ExifEntity,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
imageFrom2015: Object.freeze<AssetEntity>({
|
||||
@@ -492,7 +482,6 @@ export const assetStub = {
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
video: Object.freeze<AssetEntity>({
|
||||
@@ -533,7 +522,6 @@ export const assetStub = {
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
livePhotoMotionAsset: Object.freeze({
|
||||
@@ -625,7 +613,6 @@ export const assetStub = {
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
sidecar: Object.freeze<AssetEntity>({
|
||||
@@ -661,7 +648,6 @@ export const assetStub = {
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
sidecarWithoutExt: Object.freeze<AssetEntity>({
|
||||
@@ -697,7 +683,6 @@ export const assetStub = {
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
hasEncodedVideo: Object.freeze<AssetEntity>({
|
||||
@@ -736,7 +721,6 @@ export const assetStub = {
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
hasFileExtension: Object.freeze<AssetEntity>({
|
||||
@@ -776,7 +760,6 @@ export const assetStub = {
|
||||
} as ExifEntity,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
imageDng: Object.freeze<AssetEntity>({
|
||||
@@ -817,7 +800,6 @@ export const assetStub = {
|
||||
} as ExifEntity,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
hasEmbedding: Object.freeze<AssetEntity>({
|
||||
@@ -860,7 +842,6 @@ export const assetStub = {
|
||||
embedding: '[1, 2, 3, 4]',
|
||||
},
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
hasDupe: Object.freeze<AssetEntity>({
|
||||
@@ -903,6 +884,5 @@ export const assetStub = {
|
||||
embedding: '[1, 2, 3, 4]',
|
||||
},
|
||||
isOffline: false,
|
||||
isDirty: false,
|
||||
}),
|
||||
};
|
||||
|
||||
-1
@@ -247,7 +247,6 @@ export const sharedLinkStub = {
|
||||
sidecarPath: null,
|
||||
deletedAt: null,
|
||||
duplicateId: null,
|
||||
isDirty: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -123,7 +123,7 @@ describe(MetadataService.name, () => {
|
||||
process.env.TZ = serverTimeZone ?? undefined;
|
||||
|
||||
const { filePath } = await createTestFile(exifData);
|
||||
mocks.asset.getById.mockResolvedValue({ id: 'asset-1', originalPath: filePath } as AssetEntity);
|
||||
mocks.asset.getByIds.mockResolvedValue([{ id: 'asset-1', originalPath: filePath } as AssetEntity]);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: 'asset-1' });
|
||||
|
||||
|
||||
@@ -105,7 +105,6 @@ const assetFactory = (asset: Partial<Asset> = {}) => ({
|
||||
isFavorite: false,
|
||||
isOffline: false,
|
||||
isVisible: true,
|
||||
isDirty: false,
|
||||
libraryId: null,
|
||||
livePhotoVideoId: null,
|
||||
localDateTime: newDate(),
|
||||
|
||||
Reference in New Issue
Block a user