1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-01 08:31:59 +00:00

fix(server): scale transcoded videos if dimensions are odd (#6461)

scale if odd resolution
This commit is contained in:
Mert 2024-01-17 22:16:44 -05:00 committed by GitHub
parent b98d1bf9d3
commit 9a2fa21b28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 106 additions and 4 deletions

View file

@ -600,6 +600,66 @@ describe(MediaService.name, () => {
); );
}); });
it('should always scale video if height is uneven', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStreamOddHeight);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.ALL },
{ key: SystemConfigKey.FFMPEG_TARGET_RESOLUTION, value: 'original' },
]);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext',
'upload/encoded-video/user-id/as/se/asset-id.mp4',
{
inputOptions: [],
outputOptions: [
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
`-vf scale=-2:354,format=yuv420p`,
'-preset ultrafast',
'-crf 23',
],
twoPass: false,
},
);
});
it('should always scale video if width is uneven', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStreamOddWidth);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.ALL },
{ key: SystemConfigKey.FFMPEG_TARGET_RESOLUTION, value: 'original' },
]);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext',
'upload/encoded-video/user-id/as/se/asset-id.mp4',
{
inputOptions: [],
outputOptions: [
'-c:v h264',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-v verbose',
`-vf scale=354:-2,format=yuv420p`,
'-preset ultrafast',
'-crf 23',
],
twoPass: false,
},
);
});
it('should transcode when audio doesnt match target', async () => { it('should transcode when audio doesnt match target', async () => {
mediaMock.probe.mockResolvedValue(probeStub.audioStreamMp3); mediaMock.probe.mockResolvedValue(probeStub.audioStreamMp3);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]);

View file

@ -122,15 +122,24 @@ class BaseConfig implements VideoCodecSWConfig {
} }
getTargetResolution(videoStream: VideoStreamInfo) { getTargetResolution(videoStream: VideoStreamInfo) {
let target;
if (this.config.targetResolution === 'original') { if (this.config.targetResolution === 'original') {
return Math.min(videoStream.height, videoStream.width); target = Math.min(videoStream.height, videoStream.width);
} else {
target = Number.parseInt(this.config.targetResolution);
} }
return Number.parseInt(this.config.targetResolution); if (target % 2 !== 0) {
target -= 1;
}
return target;
} }
shouldScale(videoStream: VideoStreamInfo) { shouldScale(videoStream: VideoStreamInfo) {
return Math.min(videoStream.height, videoStream.width) > this.getTargetResolution(videoStream); const oddDimensions = videoStream.height % 2 !== 0 || videoStream.width % 2 !== 0;
const largerThanTarget = Math.min(videoStream.height, videoStream.width) > this.getTargetResolution(videoStream);
return oddDimensions || largerThanTarget;
} }
shouldToneMap(videoStream: VideoStreamInfo) { shouldToneMap(videoStream: VideoStreamInfo) {
@ -146,7 +155,10 @@ class BaseConfig implements VideoCodecSWConfig {
getSize(videoStream: VideoStreamInfo) { getSize(videoStream: VideoStreamInfo) {
const smaller = this.getTargetResolution(videoStream); const smaller = this.getTargetResolution(videoStream);
const factor = Math.max(videoStream.height, videoStream.width) / Math.min(videoStream.height, videoStream.width); const factor = Math.max(videoStream.height, videoStream.width) / Math.min(videoStream.height, videoStream.width);
const larger = Math.round(smaller * factor); let larger = Math.round(smaller * factor);
if (larger % 2 !== 0) {
larger -= 1;
}
return this.isVideoVertical(videoStream) ? { width: smaller, height: larger } : { width: larger, height: smaller }; return this.isVideoVertical(videoStream) ? { width: smaller, height: larger } : { width: larger, height: smaller };
} }

View file

@ -117,6 +117,36 @@ export const probeStub = {
}, },
], ],
}), }),
videoStreamOddHeight: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [
{
index: 0,
height: 355,
width: 1586,
codecName: 'h264',
codecType: 'video',
frameCount: 100,
rotation: 0,
isHDR: false,
},
],
}),
videoStreamOddWidth: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [
{
index: 0,
height: 1586,
width: 355,
codecName: 'h264',
codecType: 'video',
frameCount: 100,
rotation: 0,
isHDR: false,
},
],
}),
audioStreamMp3: Object.freeze<VideoInfo>({ audioStreamMp3: Object.freeze<VideoInfo>({
...probeStubDefault, ...probeStubDefault,
audioStreams: [{ index: 1, codecType: 'audio', codecName: 'aac', frameCount: 100 }], audioStreams: [{ index: 1, codecType: 'audio', codecName: 'aac', frameCount: 100 }],