You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	fix(server): scale transcoded videos if dimensions are odd (#6461)
scale if odd resolution
This commit is contained in:
		| @@ -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 () => { | ||||
|       mediaMock.probe.mockResolvedValue(probeStub.audioStreamMp3); | ||||
|       configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); | ||||
|   | ||||
| @@ -122,15 +122,24 @@ class BaseConfig implements VideoCodecSWConfig { | ||||
|   } | ||||
|  | ||||
|   getTargetResolution(videoStream: VideoStreamInfo) { | ||||
|     let target; | ||||
|     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) { | ||||
|     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) { | ||||
| @@ -146,7 +155,10 @@ class BaseConfig implements VideoCodecSWConfig { | ||||
|   getSize(videoStream: VideoStreamInfo) { | ||||
|     const smaller = this.getTargetResolution(videoStream); | ||||
|     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 }; | ||||
|   } | ||||
|  | ||||
|   | ||||
							
								
								
									
										30
									
								
								server/test/fixtures/media.stub.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								server/test/fixtures/media.stub.ts
									
									
									
									
										vendored
									
									
								
							| @@ -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>({ | ||||
|     ...probeStubDefault, | ||||
|     audioStreams: [{ index: 1, codecType: 'audio', codecName: 'aac', frameCount: 100 }], | ||||
|   | ||||
		Reference in New Issue
	
	Block a user