mirror of
https://github.com/immich-app/immich.git
synced 2026-03-26 20:00:44 +03:00
fix: writing empty exif tags (#27025)
This commit is contained in:
@@ -119,8 +119,12 @@ export class MetadataRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async writeTags(path: string, tags: Partial<Tags>): Promise<void> {
|
async writeTags(path: string, tags: Partial<Tags>): Promise<void> {
|
||||||
|
// If exiftool assigns a field with ^= instead of =, empty values will be written too.
|
||||||
|
// Since exiftool-vendored doesn't support an option for this, we append the ^ to the name of the tag instead.
|
||||||
|
// https://exiftool.org/exiftool_pod.html#:~:text=is%20used%20to%20write%20an%20empty%20string
|
||||||
|
const tagsToWrite = Object.fromEntries(Object.entries(tags).map(([key, value]) => [`${key}^`, value]));
|
||||||
try {
|
try {
|
||||||
await this.exiftool.write(path, tags);
|
await this.exiftool.write(path, tagsToWrite);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(`Error writing exif data (${path}): ${error}`);
|
this.logger.warn(`Error writing exif data (${path}): ${error}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ export class MetadataService extends BaseService {
|
|||||||
GPSLatitude: latitude,
|
GPSLatitude: latitude,
|
||||||
GPSLongitude: longitude,
|
GPSLongitude: longitude,
|
||||||
Rating: rating,
|
Rating: rating,
|
||||||
TagsList: tags?.length ? tags : undefined,
|
TagsList: tags,
|
||||||
},
|
},
|
||||||
_.isUndefined,
|
_.isUndefined,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { Kysely } from 'kysely';
|
||||||
|
import { mkdtempSync, readFileSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { MetadataRepository } from 'src/repositories/metadata.repository';
|
||||||
|
import { DB } from 'src/schema';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
|
import { newMediumService } from 'test/medium.factory';
|
||||||
|
import { newDate } from 'test/small.factory';
|
||||||
|
import { getKyselyDB } from 'test/utils';
|
||||||
|
|
||||||
|
let database: Kysely<DB>;
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const { ctx } = newMediumService(BaseService, {
|
||||||
|
database,
|
||||||
|
real: [],
|
||||||
|
mock: [LoggingRepository],
|
||||||
|
});
|
||||||
|
return { ctx, sut: ctx.get(MetadataRepository) };
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
database = await getKyselyDB();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(MetadataRepository.name, () => {
|
||||||
|
describe('writeTags', () => {
|
||||||
|
it('should write an empty description', async () => {
|
||||||
|
const { sut } = setup();
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), 'metadata-medium-write-tags'));
|
||||||
|
const sidecarFile = join(dir, 'sidecar.xmp');
|
||||||
|
|
||||||
|
await sut.writeTags(sidecarFile, { Description: '' });
|
||||||
|
expect(readFileSync(sidecarFile).toString()).toEqual(expect.stringContaining('rdf:Description'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should write an empty tags list', async () => {
|
||||||
|
const { sut } = setup();
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), 'metadata-medium-write-tags'));
|
||||||
|
const sidecarFile = join(dir, 'sidecar.xmp');
|
||||||
|
|
||||||
|
await sut.writeTags(sidecarFile, { TagsList: [] });
|
||||||
|
const fileContent = readFileSync(sidecarFile).toString();
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('digiKam:TagsList'));
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('<rdf:li/>'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should write tags', async () => {
|
||||||
|
const { sut } = setup();
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), 'metadata-medium-write-tags'));
|
||||||
|
const sidecarFile = join(dir, 'sidecar.xmp');
|
||||||
|
|
||||||
|
await sut.writeTags(sidecarFile, {
|
||||||
|
Description: 'my-description',
|
||||||
|
ImageDescription: 'my-image-description',
|
||||||
|
DateTimeOriginal: newDate().toISOString(),
|
||||||
|
GPSLatitude: 42,
|
||||||
|
GPSLongitude: 69,
|
||||||
|
Rating: 3,
|
||||||
|
TagsList: ['tagA'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileContent = readFileSync(sidecarFile).toString();
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('my-description'));
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('my-image-description'));
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('exif:DateTimeOriginal'));
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('<exif:GPSLatitude>42,0.0N</exif:GPSLatitude>'));
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('<exif:GPSLongitude>69,0.0E</exif:GPSLongitude>'));
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('<xmp:Rating>3</xmp:Rating>'));
|
||||||
|
expect(fileContent).toEqual(expect.stringContaining('tagA'));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user