mirror of
https://github.com/immich-app/immich.git
synced 2025-02-03 01:22:44 +01:00
Merge 5942d9b52c
into 92dff839d0
This commit is contained in:
commit
01f2a058a9
12 changed files with 60 additions and 14 deletions
docs/docs/install
i18n
open-api
server
web/src/lib/components/admin-page/settings/ffmpeg
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11863,6 +11863,9 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"transcodeHDR": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"twoPass": {
|
||||
"type": "boolean"
|
||||
}
|
||||
|
@ -11888,6 +11891,7 @@
|
|||
"threads",
|
||||
"tonemap",
|
||||
"transcode",
|
||||
"transcodeHDR",
|
||||
"twoPass"
|
||||
],
|
||||
"type": "object"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -106,6 +106,9 @@ export class SystemConfigFFmpegDto {
|
|||
@IsString()
|
||||
maxBitrate!: string;
|
||||
|
||||
@ValidateBoolean()
|
||||
transcodeHDR!: boolean;
|
||||
|
||||
@IsInt()
|
||||
@Min(-1)
|
||||
@Max(16)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
16
server/test/fixtures/media.stub.ts
vendored
16
server/test/fixtures/media.stub.ts
vendored
|
@ -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 }],
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue