chore: rebase and clean-up

This commit is contained in:
Jason Rasmussen
2023-11-21 21:16:40 -05:00
parent c1d9ce8679
commit b8a9cbc659
31 changed files with 928 additions and 53 deletions
@@ -253,11 +253,11 @@ describe(MetadataService.name, () => {
const originalDate = new Date('2023-11-21T16:13:17.517Z');
const sidecarDate = new Date('2022-01-01T00:00:00.000Z');
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
when(metadataMock.getExifTags)
when(metadataMock.readTags)
.calledWith(assetStub.sidecar.originalPath)
// higher priority tag
.mockResolvedValue({ CreationDate: originalDate.toISOString() });
when(metadataMock.getExifTags)
when(metadataMock.readTags)
.calledWith(assetStub.sidecar.sidecarPath as string)
// lower priority tag, but in sidecar
.mockResolvedValue({ CreateDate: sidecarDate.toISOString() });
@@ -275,7 +275,7 @@ describe(MetadataService.name, () => {
it('should handle lists of numbers', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.getExifTags.mockResolvedValue({ ISO: [160] as any });
metadataMock.readTags.mockResolvedValue({ ISO: [160] as any });
await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
@@ -292,7 +292,7 @@ describe(MetadataService.name, () => {
assetMock.getByIds.mockResolvedValue([assetStub.withLocation]);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.REVERSE_GEOCODING_ENABLED, value: true }]);
metadataMock.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' });
metadataMock.getExifTags.mockResolvedValue({
metadataMock.readTags.mockResolvedValue({
GPSLatitude: assetStub.withLocation.exifInfo!.latitude!,
GPSLongitude: assetStub.withLocation.exifInfo!.longitude!,
});
@@ -324,7 +324,7 @@ describe(MetadataService.name, () => {
it('should apply motion photos', async () => {
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
metadataMock.getExifTags.mockResolvedValue({
metadataMock.readTags.mockResolvedValue({
Directory: 'foo/bar/',
MotionPhoto: 1,
MicroVideo: 1,
@@ -345,7 +345,7 @@ describe(MetadataService.name, () => {
it('should create new motion asset if not found and link it with the photo', async () => {
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
metadataMock.getExifTags.mockResolvedValue({
metadataMock.readTags.mockResolvedValue({
Directory: 'foo/bar/',
MotionPhoto: 1,
MicroVideo: 1,
@@ -402,7 +402,7 @@ describe(MetadataService.name, () => {
tz: '+02:00',
};
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.getExifTags.mockResolvedValue(tags);
metadataMock.readTags.mockResolvedValue(tags);
await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
@@ -441,7 +441,7 @@ describe(MetadataService.name, () => {
it('should handle duration', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.getExifTags.mockResolvedValue({ Duration: 6.21 });
metadataMock.readTags.mockResolvedValue({ Duration: 6.21 });
await sut.handleMetadataExtraction({ id: assetStub.image.id });
@@ -457,7 +457,7 @@ describe(MetadataService.name, () => {
it('should handle duration as an object without Scale', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.getExifTags.mockResolvedValue({ Duration: { Value: 6.2 } });
metadataMock.readTags.mockResolvedValue({ Duration: { Value: 6.2 } });
await sut.handleMetadataExtraction({ id: assetStub.image.id });
@@ -473,7 +473,7 @@ describe(MetadataService.name, () => {
it('should handle duration with scale', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.getExifTags.mockResolvedValue({ Duration: { Scale: 1.11111111111111e-5, Value: 558720 } });
metadataMock.readTags.mockResolvedValue({ Duration: { Scale: 1.11111111111111e-5, Value: 558720 } });
await sut.handleMetadataExtraction({ id: assetStub.image.id });
+36 -3
View File
@@ -3,10 +3,11 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
import { ExifDateTime, Tags } from 'exiftool-vendored';
import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
import { constants } from 'fs/promises';
import _ from 'lodash';
import { Duration } from 'luxon';
import { Subscription } from 'rxjs';
import { usePagination } from '../domain.util';
import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
import {
ExifDuration,
IAlbumRepository,
@@ -251,6 +252,38 @@ export class MetadataService {
return true;
}
async handleSidecarWrite(job: ISidecarWriteJob) {
const { id, dateTimeOriginal, latitude, longitude } = job;
const asset = await this.assetRepository.getById(id);
if (!asset) {
return false;
}
const sidecarPath = asset.sidecarPath || `${asset.originalPath}.xmp`;
const exif = _.omitBy(
{
CreationDate: dateTimeOriginal,
GPSLatitude: latitude,
GPSLongitude: longitude,
},
_.isUndefined,
);
if (Object.keys(exif).length === 0) {
return true;
}
await this.repository.writeTags(sidecarPath, exif);
if (!asset.sidecarPath) {
await this.assetRepository.save({ id, sidecarPath });
}
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: id } });
return true;
}
private async applyReverseGeocoding(asset: AssetEntity, exifData: ExifEntityWithoutGeocodeAndTypeOrm) {
const { latitude, longitude } = exifData;
if (!(await this.configCore.hasFeature(FeatureFlag.REVERSE_GEOCODING)) || !longitude || !latitude) {
@@ -350,8 +383,8 @@ export class MetadataService {
asset: AssetEntity,
): Promise<{ exifData: ExifEntityWithoutGeocodeAndTypeOrm; tags: ImmichTags }> {
const stats = await this.storageRepository.stat(asset.originalPath);
const mediaTags = await this.repository.getExifTags(asset.originalPath);
const sidecarTags = asset.sidecarPath ? await this.repository.getExifTags(asset.sidecarPath) : null;
const mediaTags = await this.repository.readTags(asset.originalPath);
const sidecarTags = asset.sidecarPath ? await this.repository.readTags(asset.sidecarPath) : null;
// ensure date from sidecar is used if present
const hasDateOverride = !!this.getDateTimeOriginal(sidecarTags);