fix(server): clarify transcoding bitrate policy (#26711)

This commit is contained in:
Mees Frensel
2026-03-05 18:13:29 +01:00
committed by GitHub
parent a05c8c6087
commit 9b642633c1
4 changed files with 21 additions and 13 deletions

View File

@@ -411,7 +411,7 @@
"transcoding_tone_mapping": "Tone-mapping",
"transcoding_tone_mapping_description": "Attempts to preserve the appearance of HDR videos when converted to SDR. Each algorithm makes different tradeoffs for color, detail and brightness. Hable preserves detail, Mobius preserves color, and Reinhard preserves brightness.",
"transcoding_transcode_policy": "Transcode policy",
"transcoding_transcode_policy_description": "Policy for when a video should be transcoded. HDR videos will always be transcoded (except if transcoding is disabled).",
"transcoding_transcode_policy_description": "Policy for when a video should be transcoded. HDR videos and videos with a pixel format other than YUV 4:2:0 will always be transcoded (except if transcoding is disabled).",
"transcoding_two_pass_encoding": "Two-pass encoding",
"transcoding_two_pass_encoding_setting_description": "Transcode in two passes to produce better encoded videos. When max bitrate is enabled (required for it to work with H.264 and HEVC), this mode uses a bitrate range based on the max bitrate and ignores CRF. For VP9, CRF can be used if max bitrate is disabled.",
"transcoding_video_codec": "Video codec",

View File

@@ -2015,6 +2015,13 @@ describe(MediaService.name, () => {
);
});
it('should not transcode when policy bitrate and bitrate lower than max bitrate', async () => {
mocks.media.probe.mockResolvedValue(probeStub.videoStream40Mbps);
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Bitrate, maxBitrate: '50M' } });
await sut.handleVideoConversion({ id: 'video-id' });
expect(mocks.media.transcode).not.toHaveBeenCalled();
});
it('should transcode when policy bitrate and bitrate higher than max bitrate', async () => {
mocks.media.probe.mockResolvedValue(probeStub.videoStream40Mbps);
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Bitrate, maxBitrate: '30M' } });
@@ -2030,19 +2037,18 @@ describe(MediaService.name, () => {
);
});
it('should transcode when max bitrate is not a number', async () => {
it('should not transcode when max bitrate is not a number', async () => {
mocks.media.probe.mockResolvedValue(probeStub.videoStream40Mbps);
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Bitrate, maxBitrate: 'foo' } });
await sut.handleVideoConversion({ id: 'video-id' });
expect(mocks.media.transcode).toHaveBeenCalledWith(
'/original/path.ext',
expect.any(String),
expect.objectContaining({
inputOptions: expect.any(Array),
outputOptions: expect.any(Array),
twoPass: false,
}),
);
expect(mocks.media.transcode).not.toHaveBeenCalled();
});
it('should not transcode when max bitrate is 0', async () => {
mocks.media.probe.mockResolvedValue(probeStub.videoStream40Mbps);
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Bitrate, maxBitrate: '0' } });
await sut.handleVideoConversion({ id: 'video-id' });
expect(mocks.media.transcode).not.toHaveBeenCalled();
});
it('should not scale resolution if no target resolution', async () => {

View File

@@ -717,7 +717,8 @@ export class MediaService extends BaseService {
const scalingEnabled = ffmpegConfig.targetResolution !== 'original';
const targetRes = Number.parseInt(ffmpegConfig.targetResolution);
const isLargerThanTargetRes = scalingEnabled && Math.min(stream.height, stream.width) > targetRes;
const isLargerThanTargetBitrate = stream.bitrate > this.parseBitrateToBps(ffmpegConfig.maxBitrate);
const maxBitrate = this.parseBitrateToBps(ffmpegConfig.maxBitrate);
const isLargerThanTargetBitrate = maxBitrate > 0 && stream.bitrate > maxBitrate;
const isTargetVideoCodec = ffmpegConfig.acceptedVideoCodecs.includes(stream.codecName as VideoCodec);
const isRequired = !isTargetVideoCodec || !stream.pixelFormat.endsWith('420p');
@@ -769,6 +770,7 @@ export class MediaService extends BaseService {
const bitrateValue = Number.parseInt(bitrateString);
if (Number.isNaN(bitrateValue)) {
this.logger.log(`Maximum bitrate '${bitrateString} is not a number and will be ignored.`);
return 0;
}

View File

@@ -112,7 +112,7 @@ export const probeStub = {
}),
videoStream40Mbps: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [{ ...probeStubDefaultVideoStream[0], bitrate: 40_000_000 }],
videoStreams: [{ ...probeStubDefaultVideoStream[0], bitrate: 40_000_000, codecName: 'h264' }],
}),
videoStreamMTS: Object.freeze<VideoInfo>({
...probeStubDefault,