mirror of
https://github.com/immich-app/immich.git
synced 2026-01-15 22:42:31 -08:00
Compare commits
1 Commits
refactor/u
...
fix-consid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03e8f98c2c |
@@ -258,6 +258,7 @@ export class MediaRepository {
|
||||
colorPrimaries: stream.color_primaries,
|
||||
colorSpace: stream.color_space,
|
||||
colorTransfer: stream.color_transfer,
|
||||
displayAspectRatio: stream.display_aspect_ratio,
|
||||
})),
|
||||
audioStreams: results.streams
|
||||
.filter((stream) => stream.codec_type === 'audio')
|
||||
|
||||
@@ -599,6 +599,21 @@ describe(MetadataService.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply Display Aspect Ratio (DAR) for anamorphic video', async () => {
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.video);
|
||||
mocks.media.probe.mockResolvedValue(probeStub.videoStreamAnamorphic);
|
||||
mockReadTags({});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
||||
|
||||
expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.video.id);
|
||||
// Anamorphic video: 1440x1080 with DAR 16:9 should display as 1920x1080
|
||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ exifImageWidth: 1920, exifImageHeight: 1080 }),
|
||||
{ lockedPropertiesBehavior: 'skip' },
|
||||
);
|
||||
});
|
||||
|
||||
it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => {
|
||||
mocks.assetJob.getForMetadataExtraction.mockResolvedValue({
|
||||
...assetStub.livePhotoWithOriginalFileName,
|
||||
|
||||
@@ -989,12 +989,15 @@ export class MetadataService extends BaseService {
|
||||
const tags: Pick<ImmichTags, 'Duration' | 'Orientation' | 'ImageWidth' | 'ImageHeight'> = {};
|
||||
|
||||
if (videoStreams[0]) {
|
||||
// Set video dimensions
|
||||
if (videoStreams[0].width) {
|
||||
tags.ImageWidth = videoStreams[0].width;
|
||||
// Set video dimensions, considering Display Aspect Ratio (DAR) for anamorphic videos
|
||||
const { width, height, displayAspectRatio } = videoStreams[0];
|
||||
const displayDimensions = this.applyDisplayAspectRatio(width, height, displayAspectRatio);
|
||||
|
||||
if (displayDimensions.width) {
|
||||
tags.ImageWidth = displayDimensions.width;
|
||||
}
|
||||
if (videoStreams[0].height) {
|
||||
tags.ImageHeight = videoStreams[0].height;
|
||||
if (displayDimensions.height) {
|
||||
tags.ImageHeight = displayDimensions.height;
|
||||
}
|
||||
|
||||
switch (videoStreams[0].rotation) {
|
||||
@@ -1023,4 +1026,44 @@ export class MetadataService extends BaseService {
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the display dimensions of a video based on its Display Aspect Ratio (DAR).
|
||||
* DAR accounts for anamorphic videos where the stored pixel dimensions differ from the intended display dimensions.
|
||||
* For example, a 1440x1080 video with DAR 16:9 should display as 1920x1080 (1440 * 16/9 / (1440/1080) = 1920).
|
||||
*/
|
||||
private applyDisplayAspectRatio(
|
||||
width: number | undefined,
|
||||
height: number | undefined,
|
||||
displayAspectRatio: string | undefined,
|
||||
): { width?: number; height?: number } {
|
||||
if (!width || !height || !displayAspectRatio) {
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
// Parse DAR string (e.g., "16:9" or "4:3")
|
||||
const darMatch = displayAspectRatio.match(/^(\d+):(\d+)$/);
|
||||
if (!darMatch) {
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
const darWidth = Number.parseInt(darMatch[1], 10);
|
||||
const darHeight = Number.parseInt(darMatch[2], 10);
|
||||
if (!darWidth || !darHeight) {
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
const dar = darWidth / darHeight;
|
||||
const storedAspectRatio = width / height;
|
||||
|
||||
// If DAR is effectively the same as stored aspect ratio (within a small tolerance), no adjustment needed
|
||||
if (Math.abs(dar - storedAspectRatio) < 0.01) {
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
// Apply DAR by adjusting width while keeping height constant
|
||||
// This matches how video players typically handle anamorphic content
|
||||
const displayWidth = Math.round(height * dar);
|
||||
return { width: displayWidth, height };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ export interface VideoStreamInfo {
|
||||
colorPrimaries?: string;
|
||||
colorSpace?: string;
|
||||
colorTransfer?: string;
|
||||
displayAspectRatio?: string;
|
||||
}
|
||||
|
||||
export interface AudioStreamInfo {
|
||||
|
||||
17
server/test/fixtures/media.stub.ts
vendored
17
server/test/fixtures/media.stub.ts
vendored
@@ -272,4 +272,21 @@ export const probeStub = {
|
||||
},
|
||||
],
|
||||
}),
|
||||
videoStreamAnamorphic: Object.freeze<VideoInfo>({
|
||||
...probeStubDefault,
|
||||
videoStreams: [
|
||||
{
|
||||
index: 0,
|
||||
height: 1080,
|
||||
width: 1440,
|
||||
codecName: 'h264',
|
||||
frameCount: 100,
|
||||
rotation: 0,
|
||||
isHDR: false,
|
||||
bitrate: 0,
|
||||
pixelFormat: 'yuv420p',
|
||||
displayAspectRatio: '16:9',
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user