1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-02-03 01:22:44 +01:00
This commit is contained in:
Markus 2025-01-28 08:30:41 -06:00 committed by GitHub
commit 01f2a058a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 60 additions and 14 deletions

View file

@ -23,6 +23,7 @@ The default configuration looks like this:
"acceptedContainers": ["mov", "ogg", "webm"],
"targetResolution": "720",
"maxBitrate": "0",
"transcodeHDR": true,
"bframes": -1,
"refs": 0,
"gopSize": 0,

View file

@ -322,8 +322,10 @@
"transcoding_threads_description": "Höhere Werte führen zu einer schnelleren Kodierung, lassen dem Server jedoch weniger Spielraum für die Verarbeitung anderer Aufgaben im aktiven Zustand. Dieser Wert sollte nicht höher sein als die Anzahl der CPU-Kerne. Maximiert die Auslastung, wenn der Wert auf 0 gesetzt wird.",
"transcoding_tone_mapping": "Farbton-Mapping",
"transcoding_tone_mapping_description": "Versucht, das Aussehen von HDR-Videos bei der Konvertierung in SDR beizubehalten. Jeder Algorithmus geht unterschiedliche Kompromisse bei Farbe, Details und Helligkeit ein. Hable bewahrt Details, Mobius bewahrt die Farbe und Reinhard bewahrt die Helligkeit.",
"transcoding_transcode_hdr": "HDR-Videos transkodieren",
"transcoding_transcode_hdr_setting_description": "Erzwinge die Transkodierung von HDR-Videos für optimale Kompatibilität (empfohlen)",
"transcoding_transcode_policy": "Transcodierungsrichtlinie",
"transcoding_transcode_policy_description": "Richtlinie, wann ein Video transkodiert werden soll. HDR-Videos werden immer transkodiert (außer wenn die Transkodierung deaktiviert ist).",
"transcoding_transcode_policy_description": "Richtlinie, wann ein Video transkodiert werden soll. HDR-Videos werden immer transkodiert, es sei denn, diese Option wurde deaktiviert oder die Transkodierung ist insgesamt ausgeschaltet.",
"transcoding_two_pass_encoding": "Two-Pass Codierung",
"transcoding_two_pass_encoding_setting_description": "Führt eine Transkodierung in zwei Durchgängen durch, um besser kodierte Videos zu erzeugen. Wenn die maximale Bitrate aktiviert ist (erforderlich für die Verwendung mit H.264 und HEVC), verwendet dieser Modus einen Bitratenbereich, der auf der maximalen Bitrate basiert, und ignoriert CRF. Für VP9 kann CRF verwendet werden, wenn die maximale Bitrate deaktiviert ist.",
"transcoding_video_codec": "Video-Codec",

View file

@ -322,8 +322,10 @@
"transcoding_threads_description": "Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0.",
"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_hdr": "Transcode HDR Videos",
"transcoding_transcode_hdr_setting_description": "Force transcoding of HDR videos for optimal compatibility (recommended)",
"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 will always be transcoded unless disabled below or transcoding is disabled in general.",
"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",
@ -1350,4 +1352,4 @@
"yes": "Yes",
"you_dont_have_any_shared_links": "You don't have any shared links",
"zoom_image": "Zoom Image"
}
}

View file

@ -11863,6 +11863,9 @@
}
]
},
"transcodeHDR": {
"type": "boolean"
},
"twoPass": {
"type": "boolean"
}
@ -11888,6 +11891,7 @@
"threads",
"tonemap",
"transcode",
"transcodeHDR",
"twoPass"
],
"type": "object"

View file

@ -33,6 +33,7 @@ export interface SystemConfig {
acceptedContainers: VideoContainer[];
targetResolution: string;
maxBitrate: string;
transcodeHDR: boolean;
bframes: number;
refs: number;
gopSize: number;
@ -182,6 +183,7 @@ export const defaults = Object.freeze<SystemConfig>({
acceptedContainers: [VideoContainer.MOV, VideoContainer.OGG, VideoContainer.WEBM],
targetResolution: '720',
maxBitrate: '0',
transcodeHDR: true,
bframes: -1,
refs: 0,
gopSize: 0,

View file

@ -106,6 +106,9 @@ export class SystemConfigFFmpegDto {
@IsString()
maxBitrate!: string;
@ValidateBoolean()
transcodeHDR!: boolean;
@IsInt()
@Min(-1)
@Max(16)

View file

@ -2313,9 +2313,9 @@ describe(MediaService.name, () => {
);
});
it('should tonemap when policy is required and video is hdr', async () => {
it('should tonemap when policy is required, video is hdr and transcodeHDR is enabled', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
systemMock.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.REQUIRED } });
systemMock.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.REQUIRED, transcodeHDR: true } });
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(
@ -2333,9 +2333,9 @@ describe(MediaService.name, () => {
);
});
it('should tonemap when policy is optimal and video is hdr', async () => {
it('should tonemap when policy is optimal, video is hdr and transcodeHDR is enabled', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
systemMock.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.OPTIMAL } });
systemMock.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.OPTIMAL, transcodeHDR: true } });
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(
@ -2353,9 +2353,9 @@ describe(MediaService.name, () => {
);
});
it('should transcode when policy is required and video is not yuv420p', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStream10Bit);
systemMock.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.REQUIRED } });
it('should transcode when policy is required and pixelformat is not supported', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStreamYuv444p);
systemMock.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.REQUIRED, transcodeHDR: true } });
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(

View file

@ -432,9 +432,12 @@ export class MediaService extends BaseService {
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 supportedPixelFormats: string[] = ['yuv420p', 'yuvj420p', 'yuva420p', 'yuv420p10le'];
const isTargetVideoCodec = ffmpegConfig.acceptedVideoCodecs.includes(stream.codecName as VideoCodec);
const isRequired = !isTargetVideoCodec || !stream.pixelFormat.endsWith('420p');
const isTargetDynamicRange = !ffmpegConfig.transcodeHDR || !stream.isHDR;
const isRequired =
!isTargetVideoCodec || !isTargetDynamicRange || !supportedPixelFormats.includes(stream.pixelFormat);
switch (ffmpegConfig.transcode) {
case TranscodePolicy.DISABLED: {

View file

@ -60,6 +60,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
acceptedVideoCodecs: [VideoCodec.H264],
acceptedContainers: [VideoContainer.MOV, VideoContainer.OGG, VideoContainer.WEBM],
maxBitrate: '0',
transcodeHDR: true,
bframes: -1,
refs: 0,
gopSize: 0,

View file

@ -159,9 +159,13 @@ export class BaseConfig implements VideoCodecSWConfig {
options.push(`scale=${this.getScaling(videoStream)}`);
}
options.push(...this.getToneMapping(videoStream));
if (options.length === 0 && !videoStream.pixelFormat.endsWith('420p')) {
options.push(`format=yuv420p`);
if (!this.config.transcodeHDR && videoStream.isHDR) {
options.push(`format=yuv420p10le`);
} else {
options.push(...this.getToneMapping(videoStream));
if (options.length === 0 && !videoStream.pixelFormat.endsWith('420p')) {
options.push(`format=yuv420p`);
}
}
return options;

View file

@ -182,6 +182,22 @@ export const probeStub = {
},
],
}),
videoStreamYuv444p: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [
{
index: 0,
height: 480,
width: 480,
codecName: 'h264',
frameCount: 100,
rotation: 0,
isHDR: false,
bitrate: 0,
pixelFormat: 'yuv444p',
},
],
}),
audioStreamAac: Object.freeze<VideoInfo>({
...probeStubDefault,
audioStreams: [{ index: 1, codecName: 'aac', frameCount: 100 }],

View file

@ -151,6 +151,14 @@
sortBy(savedConfig.ffmpeg.acceptedContainers),
)}
/>
<SettingSwitch
title={$t('admin.transcoding_transcode_hdr')}
{disabled}
subtitle={$t('admin.transcoding_transcode_hdr_setting_description')}
bind:checked={config.ffmpeg.transcodeHDR}
isEdited={config.ffmpeg.transcodeHDR !== savedConfig.ffmpeg.transcodeHDR}
/>
</div>
</SettingAccordion>