mirror of
https://github.com/immich-app/immich.git
synced 2025-12-05 20:40:29 -08:00
fix: duration extraction (#24178)
This commit is contained in:
@@ -1017,12 +1017,44 @@ describe(MetadataService.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should ignore duration from exif data', async () => {
|
||||
it('should use Duration from exif', async () => {
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image);
|
||||
mockReadTags({}, { Duration: { Value: 123 } });
|
||||
mockReadTags({ Duration: 123 }, {});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: null }));
|
||||
|
||||
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:02:03.000' }));
|
||||
});
|
||||
|
||||
it('should prefer Duration from exif over sidecar', async () => {
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue({
|
||||
...assetStub.image,
|
||||
sidecarPath: '/path/to/something',
|
||||
});
|
||||
mockReadTags({ Duration: 123 }, { Duration: 456 });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
|
||||
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:02:03.000' }));
|
||||
});
|
||||
|
||||
it('should ignore Duration from exif for videos', async () => {
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.video);
|
||||
mockReadTags({ Duration: 123 }, {});
|
||||
mocks.media.probe.mockResolvedValue({
|
||||
...probeStub.videoStreamH264,
|
||||
format: {
|
||||
...probeStub.videoStreamH264.format,
|
||||
duration: 456,
|
||||
},
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
||||
|
||||
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:07:36.000' }));
|
||||
});
|
||||
|
||||
it('should trim whitespace from description', async () => {
|
||||
|
||||
@@ -291,7 +291,7 @@ export class MetadataService extends BaseService {
|
||||
this.assetRepository.upsertExif(exifData),
|
||||
this.assetRepository.update({
|
||||
id: asset.id,
|
||||
duration: exifTags.Duration?.toString() ?? null,
|
||||
duration: this.getDuration(exifTags),
|
||||
localDateTime: dates.localDateTime,
|
||||
fileCreatedAt: dates.dateTimeOriginal ?? undefined,
|
||||
fileModifiedAt: stats.mtime,
|
||||
@@ -457,19 +457,7 @@ export class MetadataService extends BaseService {
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
private getExifTags(asset: {
|
||||
originalPath: string;
|
||||
sidecarPath: string | null;
|
||||
type: AssetType;
|
||||
}): Promise<ImmichTags> {
|
||||
if (!asset.sidecarPath && asset.type === AssetType.Image) {
|
||||
return this.metadataRepository.readTags(asset.originalPath);
|
||||
}
|
||||
|
||||
return this.mergeExifTags(asset);
|
||||
}
|
||||
|
||||
private async mergeExifTags(asset: {
|
||||
private async getExifTags(asset: {
|
||||
originalPath: string;
|
||||
sidecarPath: string | null;
|
||||
type: AssetType;
|
||||
@@ -492,7 +480,11 @@ export class MetadataService extends BaseService {
|
||||
}
|
||||
|
||||
// prefer duration from video tags
|
||||
if (videoTags) {
|
||||
delete mediaTags.Duration;
|
||||
}
|
||||
|
||||
// never use duration from sidecar
|
||||
delete sidecarTags?.Duration;
|
||||
|
||||
return { ...mediaTags, ...videoTags, ...sidecarTags };
|
||||
@@ -934,6 +926,20 @@ export class MetadataService extends BaseService {
|
||||
return bitsPerSample;
|
||||
}
|
||||
|
||||
private getDuration(tags: ImmichTags): string | null {
|
||||
const duration = tags.Duration;
|
||||
|
||||
if (typeof duration === 'string') {
|
||||
return duration;
|
||||
}
|
||||
|
||||
if (typeof duration === 'number') {
|
||||
return Duration.fromObject({ seconds: duration }).toFormat('hh:mm:ss.SSS');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async getVideoTags(originalPath: string) {
|
||||
const { videoStreams, format } = await this.mediaRepository.probe(originalPath);
|
||||
|
||||
@@ -961,7 +967,7 @@ export class MetadataService extends BaseService {
|
||||
}
|
||||
|
||||
if (format.duration) {
|
||||
tags.Duration = Duration.fromObject({ seconds: format.duration }).toFormat('hh:mm:ss.SSS');
|
||||
tags.Duration = format.duration;
|
||||
}
|
||||
|
||||
return tags;
|
||||
|
||||
Reference in New Issue
Block a user