mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -04:00 
			
		
		
		
	fix(server): select main stream according to bitrate (#18375)
* fix main stream * update unit tests --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									00a77c2d6a
								
							
						
					
					
						commit
						dc8962f2bc
					
				| @ -209,7 +209,7 @@ export class MediaRepository { | |||||||
|           index: stream.index, |           index: stream.index, | ||||||
|           codecType: stream.codec_type, |           codecType: stream.codec_type, | ||||||
|           codecName: stream.codec_name, |           codecName: stream.codec_name, | ||||||
|           frameCount: this.parseInt(options?.countFrames ? stream.nb_read_packets : stream.nb_frames), |           bitrate: this.parseInt(stream.bit_rate), | ||||||
|         })), |         })), | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -1235,7 +1235,7 @@ describe(MediaService.name, () => { | |||||||
|       expect(mocks.media.transcode).not.toHaveBeenCalled(); |       expect(mocks.media.transcode).not.toHaveBeenCalled(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should transcode the longest stream', async () => { |     it('should transcode the highest bitrate video stream', async () => { | ||||||
|       mocks.logger.isLevelEnabled.mockReturnValue(false); |       mocks.logger.isLevelEnabled.mockReturnValue(false); | ||||||
|       mocks.media.probe.mockResolvedValue(probeStub.multipleVideoStreams); |       mocks.media.probe.mockResolvedValue(probeStub.multipleVideoStreams); | ||||||
| 
 | 
 | ||||||
| @ -1249,7 +1249,27 @@ describe(MediaService.name, () => { | |||||||
|         'upload/encoded-video/user-id/as/se/asset-id.mp4', |         'upload/encoded-video/user-id/as/se/asset-id.mp4', | ||||||
|         expect.objectContaining({ |         expect.objectContaining({ | ||||||
|           inputOptions: expect.any(Array), |           inputOptions: expect.any(Array), | ||||||
|           outputOptions: expect.arrayContaining(['-map 0:0', '-map 0:1']), |           outputOptions: expect.arrayContaining(['-map 0:1', '-map 0:3']), | ||||||
|  |           twoPass: false, | ||||||
|  |         }), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should transcode the highest bitrate audio stream', async () => { | ||||||
|  |       mocks.logger.isLevelEnabled.mockReturnValue(false); | ||||||
|  |       mocks.media.probe.mockResolvedValue(probeStub.multipleAudioStreams); | ||||||
|  | 
 | ||||||
|  |       await sut.handleVideoConversion({ id: assetStub.video.id }); | ||||||
|  | 
 | ||||||
|  |       expect(mocks.media.probe).toHaveBeenCalledWith('/original/path.ext', { countFrames: false }); | ||||||
|  |       expect(mocks.systemMetadata.get).toHaveBeenCalled(); | ||||||
|  |       expect(mocks.storage.mkdirSync).toHaveBeenCalled(); | ||||||
|  |       expect(mocks.media.transcode).toHaveBeenCalledWith( | ||||||
|  |         '/original/path.ext', | ||||||
|  |         'upload/encoded-video/user-id/as/se/asset-id.mp4', | ||||||
|  |         expect.objectContaining({ | ||||||
|  |           inputOptions: expect.any(Array), | ||||||
|  |           outputOptions: expect.arrayContaining(['-map 0:0', '-map 0:2']), | ||||||
|           twoPass: false, |           twoPass: false, | ||||||
|         }), |         }), | ||||||
|       ); |       ); | ||||||
| @ -1780,7 +1800,7 @@ describe(MediaService.name, () => { | |||||||
|             '-movflags faststart', |             '-movflags faststart', | ||||||
|             '-fps_mode passthrough', |             '-fps_mode passthrough', | ||||||
|             '-map 0:0', |             '-map 0:0', | ||||||
|             '-map 0:1', |             '-map 0:3', | ||||||
|             '-v verbose', |             '-v verbose', | ||||||
|             '-vf scale=-2:720', |             '-vf scale=-2:720', | ||||||
|             '-preset 12', |             '-preset 12', | ||||||
| @ -1901,7 +1921,7 @@ describe(MediaService.name, () => { | |||||||
|             '-movflags faststart', |             '-movflags faststart', | ||||||
|             '-fps_mode passthrough', |             '-fps_mode passthrough', | ||||||
|             '-map 0:0', |             '-map 0:0', | ||||||
|             '-map 0:1', |             '-map 0:3', | ||||||
|             '-g 256', |             '-g 256', | ||||||
|             '-v verbose', |             '-v verbose', | ||||||
|             '-vf hwupload_cuda,scale_cuda=-2:720:format=nv12', |             '-vf hwupload_cuda,scale_cuda=-2:720:format=nv12', | ||||||
| @ -2072,7 +2092,7 @@ describe(MediaService.name, () => { | |||||||
|             '-movflags faststart', |             '-movflags faststart', | ||||||
|             '-fps_mode passthrough', |             '-fps_mode passthrough', | ||||||
|             '-map 0:0', |             '-map 0:0', | ||||||
|             '-map 0:1', |             '-map 0:3', | ||||||
|             '-bf 7', |             '-bf 7', | ||||||
|             '-refs 5', |             '-refs 5', | ||||||
|             '-g 256', |             '-g 256', | ||||||
| @ -2294,7 +2314,7 @@ describe(MediaService.name, () => { | |||||||
|             '-movflags faststart', |             '-movflags faststart', | ||||||
|             '-fps_mode passthrough', |             '-fps_mode passthrough', | ||||||
|             '-map 0:0', |             '-map 0:0', | ||||||
|             '-map 0:1', |             '-map 0:3', | ||||||
|             '-g 256', |             '-g 256', | ||||||
|             '-v verbose', |             '-v verbose', | ||||||
|             '-vf hwupload=extra_hw_frames=64,scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12', |             '-vf hwupload=extra_hw_frames=64,scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12', | ||||||
| @ -2581,7 +2601,7 @@ describe(MediaService.name, () => { | |||||||
|             '-movflags faststart', |             '-movflags faststart', | ||||||
|             '-fps_mode passthrough', |             '-fps_mode passthrough', | ||||||
|             '-map 0:0', |             '-map 0:0', | ||||||
|             '-map 0:1', |             '-map 0:3', | ||||||
|             '-g 256', |             '-g 256', | ||||||
|             '-v verbose', |             '-v verbose', | ||||||
|             '-vf scale_rkrga=-2:720:format=nv12:afbc=1:async_depth=4', |             '-vf scale_rkrga=-2:720:format=nv12:afbc=1:async_depth=4', | ||||||
|  | |||||||
| @ -547,7 +547,7 @@ export class MediaService extends BaseService { | |||||||
|   private getMainStream<T extends VideoStreamInfo | AudioStreamInfo>(streams: T[]): T { |   private getMainStream<T extends VideoStreamInfo | AudioStreamInfo>(streams: T[]): T { | ||||||
|     return streams |     return streams | ||||||
|       .filter((stream) => stream.codecName !== 'unknown') |       .filter((stream) => stream.codecName !== 'unknown') | ||||||
|       .sort((stream1, stream2) => stream2.frameCount - stream1.frameCount)[0]; |       .sort((stream1, stream2) => stream2.bitrate - stream1.bitrate)[0]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private getTranscodeTarget( |   private getTranscodeTarget( | ||||||
|  | |||||||
| @ -89,7 +89,7 @@ export interface VideoStreamInfo { | |||||||
| export interface AudioStreamInfo { | export interface AudioStreamInfo { | ||||||
|   index: number; |   index: number; | ||||||
|   codecName?: string; |   codecName?: string; | ||||||
|   frameCount: number; |   bitrate: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface VideoFormat { | export interface VideoFormat { | ||||||
|  | |||||||
							
								
								
									
										37
									
								
								server/test/fixtures/media.stub.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								server/test/fixtures/media.stub.ts
									
									
									
									
										vendored
									
									
								
							| @ -21,7 +21,7 @@ const probeStubDefaultVideoStream: VideoStreamInfo[] = [ | |||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| const probeStubDefaultAudioStream: AudioStreamInfo[] = [{ index: 1, codecName: 'mp3', frameCount: 100 }]; | const probeStubDefaultAudioStream: AudioStreamInfo[] = [{ index: 3, codecName: 'mp3', bitrate: 100 }]; | ||||||
| 
 | 
 | ||||||
| const probeStubDefault: VideoInfo = { | const probeStubDefault: VideoInfo = { | ||||||
|   format: probeStubDefaultFormat, |   format: probeStubDefaultFormat, | ||||||
| @ -40,23 +40,42 @@ export const probeStub = { | |||||||
|         height: 1080, |         height: 1080, | ||||||
|         width: 400, |         width: 400, | ||||||
|         codecName: 'hevc', |         codecName: 'hevc', | ||||||
|         frameCount: 100, |         frameCount: 1, | ||||||
|         rotation: 0, |         rotation: 0, | ||||||
|         isHDR: false, |         isHDR: false, | ||||||
|         bitrate: 0, |         bitrate: 100, | ||||||
|         pixelFormat: 'yuv420p', |         pixelFormat: 'yuv420p', | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         index: 1, |         index: 1, | ||||||
|         height: 1080, |         height: 1080, | ||||||
|         width: 400, |         width: 400, | ||||||
|         codecName: 'h7000', |         codecName: 'hevc', | ||||||
|         frameCount: 99, |         frameCount: 2, | ||||||
|         rotation: 0, |         rotation: 0, | ||||||
|         isHDR: false, |         isHDR: false, | ||||||
|         bitrate: 0, |         bitrate: 101, | ||||||
|         pixelFormat: 'yuv420p', |         pixelFormat: 'yuv420p', | ||||||
|       }, |       }, | ||||||
|  |       { | ||||||
|  |         index: 2, | ||||||
|  |         height: 1080, | ||||||
|  |         width: 400, | ||||||
|  |         codecName: 'h7000', | ||||||
|  |         frameCount: 3, | ||||||
|  |         rotation: 0, | ||||||
|  |         isHDR: false, | ||||||
|  |         bitrate: 99, | ||||||
|  |         pixelFormat: 'yuv420p', | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }), | ||||||
|  |   multipleAudioStreams: Object.freeze<VideoInfo>({ | ||||||
|  |     ...probeStubDefault, | ||||||
|  |     audioStreams: [ | ||||||
|  |       { index: 0, codecName: 'mp3', bitrate: 100 }, | ||||||
|  |       { index: 1, codecName: 'mp3', bitrate: 101 }, | ||||||
|  |       { index: 2, codecName: 'mp3', bitrate: 102 }, | ||||||
|     ], |     ], | ||||||
|   }), |   }), | ||||||
|   noHeight: Object.freeze<VideoInfo>({ |   noHeight: Object.freeze<VideoInfo>({ | ||||||
| @ -200,13 +219,13 @@ export const probeStub = { | |||||||
|   }), |   }), | ||||||
|   audioStreamAac: Object.freeze<VideoInfo>({ |   audioStreamAac: Object.freeze<VideoInfo>({ | ||||||
|     ...probeStubDefault, |     ...probeStubDefault, | ||||||
|     audioStreams: [{ index: 1, codecName: 'aac', frameCount: 100 }], |     audioStreams: [{ index: 1, codecName: 'aac', bitrate: 100 }], | ||||||
|   }), |   }), | ||||||
|   audioStreamUnknown: Object.freeze<VideoInfo>({ |   audioStreamUnknown: Object.freeze<VideoInfo>({ | ||||||
|     ...probeStubDefault, |     ...probeStubDefault, | ||||||
|     audioStreams: [ |     audioStreams: [ | ||||||
|       { index: 0, codecName: 'aac', frameCount: 100 }, |       { index: 0, codecName: 'aac', bitrate: 100 }, | ||||||
|       { index: 1, codecName: 'unknown', frameCount: 200 }, |       { index: 1, codecName: 'unknown', bitrate: 200 }, | ||||||
|     ], |     ], | ||||||
|   }), |   }), | ||||||
|   matroskaContainer: Object.freeze<VideoInfo>({ |   matroskaContainer: Object.freeze<VideoInfo>({ | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user