fix(server): copy relevant panorama tags to preview image (#23953)

This commit is contained in:
Mees Frensel 2025-11-19 04:02:12 +01:00 committed by GitHub
parent 4462952564
commit 271a42ac7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 36 additions and 0 deletions

View File

@ -121,6 +121,23 @@ export class MediaRepository {
} }
} }
async copyTagGroup(tagGroup: string, source: string, target: string): Promise<boolean> {
try {
await exiftool.write(
target,
{},
{
ignoreMinorErrors: true,
writeArgs: ['-TagsFromFile', source, `-${tagGroup}:all>${tagGroup}:all`, '-overwrite_original'],
},
);
return true;
} catch (error: any) {
this.logger.warn(`Could not copy tag data to image: ${error.message}`);
return false;
}
}
decodeImage(input: string | Buffer, options: DecodeToBufferOptions) { decodeImage(input: string | Buffer, options: DecodeToBufferOptions) {
return this.getImageDecodingPipeline(input, options).raw().toBuffer({ resolveWithObject: true }); return this.getImageDecodingPipeline(input, options).raw().toBuffer({ resolveWithObject: true });
} }

View File

@ -865,6 +865,7 @@ describe(MediaService.name, () => {
mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: false } } }); mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: false } } });
mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg });
mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 });
mocks.media.copyTagGroup.mockResolvedValue(true);
mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.panoramaTif); mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.panoramaTif);
@ -890,6 +891,13 @@ describe(MediaService.name, () => {
}, },
expect.any(String), expect.any(String),
); );
expect(mocks.media.copyTagGroup).toHaveBeenCalledTimes(2);
expect(mocks.media.copyTagGroup).toHaveBeenCalledWith(
'XMP-GPano',
assetStub.panoramaTif.originalPath,
expect.any(String),
);
}); });
it('should respect encoding options when generating full-size preview', async () => { it('should respect encoding options when generating full-size preview', async () => {

View File

@ -316,6 +316,16 @@ export class MediaService extends BaseService {
const outputs = await Promise.all(promises); const outputs = await Promise.all(promises);
if (asset.exifInfo.projectionType === 'EQUIRECTANGULAR') {
const promises = [
this.mediaRepository.copyTagGroup('XMP-GPano', asset.originalPath, previewPath),
fullsizePath
? this.mediaRepository.copyTagGroup('XMP-GPano', asset.originalPath, fullsizePath)
: Promise.resolve(),
];
await Promise.all(promises);
}
return { previewPath, thumbnailPath, fullsizePath, thumbhash: outputs[0] as Buffer }; return { previewPath, thumbnailPath, fullsizePath, thumbhash: outputs[0] as Buffer };
} }

View File

@ -6,6 +6,7 @@ export const newMediaRepositoryMock = (): Mocked<RepositoryInterface<MediaReposi
return { return {
generateThumbnail: vitest.fn().mockImplementation(() => Promise.resolve()), generateThumbnail: vitest.fn().mockImplementation(() => Promise.resolve()),
writeExif: vitest.fn().mockImplementation(() => Promise.resolve()), writeExif: vitest.fn().mockImplementation(() => Promise.resolve()),
copyTagGroup: vitest.fn().mockImplementation(() => Promise.resolve()),
generateThumbhash: vitest.fn().mockResolvedValue(Buffer.from('')), generateThumbhash: vitest.fn().mockResolvedValue(Buffer.from('')),
decodeImage: vitest.fn().mockResolvedValue({ data: Buffer.from(''), info: {} }), decodeImage: vitest.fn().mockResolvedValue({ data: Buffer.from(''), info: {} }),
extract: vitest.fn().mockResolvedValue(null), extract: vitest.fn().mockResolvedValue(null),