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:
Daimolean 2025-05-20 05:33:28 +08:00 committed by GitHub
parent 00a77c2d6a
commit dc8962f2bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 19 deletions

View File

@ -209,7 +209,7 @@ export class MediaRepository {
index: stream.index,
codecType: stream.codec_type,
codecName: stream.codec_name,
frameCount: this.parseInt(options?.countFrames ? stream.nb_read_packets : stream.nb_frames),
bitrate: this.parseInt(stream.bit_rate),
})),
};
}

View File

@ -1235,7 +1235,7 @@ describe(MediaService.name, () => {
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.media.probe.mockResolvedValue(probeStub.multipleVideoStreams);
@ -1249,7 +1249,27 @@ describe(MediaService.name, () => {
'upload/encoded-video/user-id/as/se/asset-id.mp4',
expect.objectContaining({
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,
}),
);
@ -1780,7 +1800,7 @@ describe(MediaService.name, () => {
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-map 0:3',
'-v verbose',
'-vf scale=-2:720',
'-preset 12',
@ -1901,7 +1921,7 @@ describe(MediaService.name, () => {
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-map 0:3',
'-g 256',
'-v verbose',
'-vf hwupload_cuda,scale_cuda=-2:720:format=nv12',
@ -2072,7 +2092,7 @@ describe(MediaService.name, () => {
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-map 0:3',
'-bf 7',
'-refs 5',
'-g 256',
@ -2294,7 +2314,7 @@ describe(MediaService.name, () => {
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-map 0:3',
'-g 256',
'-v verbose',
'-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',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-map 0:3',
'-g 256',
'-v verbose',
'-vf scale_rkrga=-2:720:format=nv12:afbc=1:async_depth=4',

View File

@ -547,7 +547,7 @@ export class MediaService extends BaseService {
private getMainStream<T extends VideoStreamInfo | AudioStreamInfo>(streams: T[]): T {
return streams
.filter((stream) => stream.codecName !== 'unknown')
.sort((stream1, stream2) => stream2.frameCount - stream1.frameCount)[0];
.sort((stream1, stream2) => stream2.bitrate - stream1.bitrate)[0];
}
private getTranscodeTarget(

View File

@ -89,7 +89,7 @@ export interface VideoStreamInfo {
export interface AudioStreamInfo {
index: number;
codecName?: string;
frameCount: number;
bitrate: number;
}
export interface VideoFormat {

View File

@ -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 = {
format: probeStubDefaultFormat,
@ -40,23 +40,42 @@ export const probeStub = {
height: 1080,
width: 400,
codecName: 'hevc',
frameCount: 100,
frameCount: 1,
rotation: 0,
isHDR: false,
bitrate: 0,
bitrate: 100,
pixelFormat: 'yuv420p',
},
{
index: 1,
height: 1080,
width: 400,
codecName: 'h7000',
frameCount: 99,
codecName: 'hevc',
frameCount: 2,
rotation: 0,
isHDR: false,
bitrate: 0,
bitrate: 101,
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>({
@ -200,13 +219,13 @@ export const probeStub = {
}),
audioStreamAac: Object.freeze<VideoInfo>({
...probeStubDefault,
audioStreams: [{ index: 1, codecName: 'aac', frameCount: 100 }],
audioStreams: [{ index: 1, codecName: 'aac', bitrate: 100 }],
}),
audioStreamUnknown: Object.freeze<VideoInfo>({
...probeStubDefault,
audioStreams: [
{ index: 0, codecName: 'aac', frameCount: 100 },
{ index: 1, codecName: 'unknown', frameCount: 200 },
{ index: 0, codecName: 'aac', bitrate: 100 },
{ index: 1, codecName: 'unknown', bitrate: 200 },
],
}),
matroskaContainer: Object.freeze<VideoInfo>({