From 8aef92affc895eb8c0d7a287512af93a8d87178e Mon Sep 17 00:00:00 2001 From: Markus Date: Fri, 26 Jan 2024 18:02:56 +0100 Subject: [PATCH] feat(server, web): accepted codecs (#6460) * chore: rebase * chore: open api --------- Co-authored-by: Jason Rasmussen --- mobile/openapi/doc/SystemConfigFFmpegDto.md | Bin 1054 -> 1236 bytes .../lib/model/system_config_f_fmpeg_dto.dart | Bin 6869 -> 7691 bytes .../test/system_config_f_fmpeg_dto_test.dart | Bin 2219 -> 2537 bytes open-api/immich-openapi-specs.json | 14 +++++ open-api/typescript-sdk/client/api.ts | 12 ++++ server/src/domain/media/media.service.ts | 6 +- .../dto/system-config-ffmpeg.dto.ts | 8 +++ .../system-config/system-config.core.ts | 10 ++++ .../system-config.service.spec.ts | 2 + .../infra/entities/system-config.entity.ts | 4 ++ .../admin-page/settings/admin-settings.svelte | 10 ++-- .../settings/ffmpeg/ffmpeg-settings.svelte | 37 +++++++++++- .../settings/setting-checkboxes.svelte | 54 ++++++++++++++++++ .../admin-page/settings/setting-select.svelte | 4 ++ 14 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 web/src/lib/components/admin-page/settings/setting-checkboxes.svelte diff --git a/mobile/openapi/doc/SystemConfigFFmpegDto.md b/mobile/openapi/doc/SystemConfigFFmpegDto.md index b5c06b95d129789704b40df9ffb356125d9abe60..25726bb3daf84d5e737743b0c33d14a640d36400 100644 GIT binary patch delta 154 zcmbQoafNe(9HVw(a&l@xNotB?X-a0kbAC!{aEOKcFP*(qSL!I}$bE?6PLG=xHgSn=jw=BteL@hPdP z1+Imqi8+}im3pZlMzKbINk(cBHs|YLvr$t)O%3c9BFsXvA8eK&IDn8uH8)4GPGPdZ z;xWC9#NrH4C~7KbgRRqmDMHe|`40PU#vqskBB+XORj>#myn|!}LM4*m=Bu118SAka zp&kp2Qj|zUveX*vP6dUm;{3emB6XN{By%yfBT1rZPue`6+m!{IBdinKZu5R?kBxXelc;SyvsR@tO1i&0}itc0@wkwHU!WCvz`Ww0<&}p=>fB`3&#Po_6}BV6DU-50jlwW^sv)V`)lezH@#`YObMi%2MIlTblO5UhASQ=prlg{qj1VK%WP}co$(wzc;@JR8jzr)9 delta 12 TcmaDUyjpOB57Xu@=6E&$AtVG` diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 03f6811bb2..175ace4349 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -9386,6 +9386,18 @@ "accel": { "$ref": "#/components/schemas/TranscodeHWAccel" }, + "acceptedAudioCodecs": { + "items": { + "$ref": "#/components/schemas/AudioCodec" + }, + "type": "array" + }, + "acceptedVideoCodecs": { + "items": { + "$ref": "#/components/schemas/VideoCodec" + }, + "type": "array" + }, "bframes": { "type": "integer" }, @@ -9437,6 +9449,8 @@ }, "required": [ "accel", + "acceptedAudioCodecs", + "acceptedVideoCodecs", "bframes", "cqMode", "crf", diff --git a/open-api/typescript-sdk/client/api.ts b/open-api/typescript-sdk/client/api.ts index 5be129b3a2..16eda5d9a7 100644 --- a/open-api/typescript-sdk/client/api.ts +++ b/open-api/typescript-sdk/client/api.ts @@ -3670,6 +3670,18 @@ export interface SystemConfigFFmpegDto { * @memberof SystemConfigFFmpegDto */ 'accel': TranscodeHWAccel; + /** + * + * @type {Array} + * @memberof SystemConfigFFmpegDto + */ + 'acceptedAudioCodecs': Array; + /** + * + * @type {Array} + * @memberof SystemConfigFFmpegDto + */ + 'acceptedVideoCodecs': Array; /** * * @type {number} diff --git a/server/src/domain/media/media.service.ts b/server/src/domain/media/media.service.ts index 8b66d6957a..dc6f3eed20 100644 --- a/server/src/domain/media/media.service.ts +++ b/server/src/domain/media/media.service.ts @@ -2,6 +2,7 @@ import { AssetEntity, AssetPathType, AssetType, + AudioCodec, Colorspace, TranscodeHWAccel, TranscodePolicy, @@ -326,9 +327,10 @@ export class MediaService { containerExtension: string, ffmpegConfig: SystemConfigFFmpegDto, ): boolean { - const isTargetVideoCodec = videoStream.codecName === ffmpegConfig.targetVideoCodec; + const isTargetVideoCodec = ffmpegConfig.acceptedVideoCodecs.includes(videoStream.codecName as VideoCodec); const isTargetContainer = ['mov,mp4,m4a,3gp,3g2,mj2', 'mp4', 'mov'].includes(containerExtension); - const isTargetAudioCodec = audioStream == null || audioStream.codecName === ffmpegConfig.targetAudioCodec; + const isTargetAudioCodec = + audioStream == null || ffmpegConfig.acceptedAudioCodecs.includes(audioStream.codecName as AudioCodec); this.logger.verbose( `${asset.id}: AudioCodecName ${audioStream?.codecName ?? 'None'}, AudioStreamCodecType ${ diff --git a/server/src/domain/system-config/dto/system-config-ffmpeg.dto.ts b/server/src/domain/system-config/dto/system-config-ffmpeg.dto.ts index cd1d1c52fa..e82ce4d7e9 100644 --- a/server/src/domain/system-config/dto/system-config-ffmpeg.dto.ts +++ b/server/src/domain/system-config/dto/system-config-ffmpeg.dto.ts @@ -24,10 +24,18 @@ export class SystemConfigFFmpegDto { @ApiProperty({ enumName: 'VideoCodec', enum: VideoCodec }) targetVideoCodec!: VideoCodec; + @IsEnum(VideoCodec, { each: true }) + @ApiProperty({ enumName: 'VideoCodec', enum: VideoCodec, isArray: true }) + acceptedVideoCodecs!: VideoCodec[]; + @IsEnum(AudioCodec) @ApiProperty({ enumName: 'AudioCodec', enum: AudioCodec }) targetAudioCodec!: AudioCodec; + @IsEnum(AudioCodec, { each: true }) + @ApiProperty({ enumName: 'AudioCodec', enum: AudioCodec, isArray: true }) + acceptedAudioCodecs!: AudioCodec[]; + @IsString() targetResolution!: string; diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index bac10c7990..0d0af03df3 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -31,7 +31,9 @@ export const defaults = Object.freeze({ threads: 0, preset: 'ultrafast', targetVideoCodec: VideoCodec.H264, + acceptedVideoCodecs: [VideoCodec.H264], targetAudioCodec: AudioCodec.AAC, + acceptedAudioCodecs: [AudioCodec.AAC], targetResolution: '720', maxBitrate: '0', bframes: -1, @@ -248,6 +250,14 @@ export class SystemConfigCore { } } + if (!config.ffmpeg.acceptedVideoCodecs.includes(config.ffmpeg.targetVideoCodec)) { + config.ffmpeg.acceptedVideoCodecs.unshift(config.ffmpeg.targetVideoCodec); + } + + if (!config.ffmpeg.acceptedAudioCodecs.includes(config.ffmpeg.targetAudioCodec)) { + config.ffmpeg.acceptedAudioCodecs.unshift(config.ffmpeg.targetAudioCodec); + } + return config; } diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index 520e198c0b..d32fcb82e5 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -43,8 +43,10 @@ const updatedConfig = Object.freeze({ threads: 0, preset: 'ultrafast', targetAudioCodec: AudioCodec.AAC, + acceptedAudioCodecs: [AudioCodec.AAC], targetResolution: '720', targetVideoCodec: VideoCodec.H264, + acceptedVideoCodecs: [VideoCodec.H264], maxBitrate: '0', bframes: -1, refs: 0, diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index b7d49e563f..e280d0ce7f 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -18,7 +18,9 @@ export enum SystemConfigKey { FFMPEG_THREADS = 'ffmpeg.threads', FFMPEG_PRESET = 'ffmpeg.preset', FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg.targetVideoCodec', + FFMPEG_ACCEPTED_VIDEO_CODECS = 'ffmpeg.acceptedVideoCodecs', FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg.targetAudioCodec', + FFMPEG_ACCEPTED_AUDIO_CODECS = 'ffmpeg.acceptedAudioCodecs', FFMPEG_TARGET_RESOLUTION = 'ffmpeg.targetResolution', FFMPEG_MAX_BITRATE = 'ffmpeg.maxBitrate', FFMPEG_BFRAMES = 'ffmpeg.bframes', @@ -162,7 +164,9 @@ export interface SystemConfig { threads: number; preset: string; targetVideoCodec: VideoCodec; + acceptedVideoCodecs: VideoCodec[]; targetAudioCodec: AudioCodec; + acceptedAudioCodecs: AudioCodec[]; targetResolution: string; maxBitrate: string; bframes: number; diff --git a/web/src/lib/components/admin-page/settings/admin-settings.svelte b/web/src/lib/components/admin-page/settings/admin-settings.svelte index 5c47c0f820..98e202336a 100644 --- a/web/src/lib/components/admin-page/settings/admin-settings.svelte +++ b/web/src/lib/components/admin-page/settings/admin-settings.svelte @@ -9,6 +9,7 @@ import { handleError } from '$lib/utils/handle-error'; import type { SettingsEventType } from './admin-settings'; import { createEventDispatcher, onMount } from 'svelte'; + import { cloneDeep } from 'lodash-es'; export let config: SystemConfigDto; @@ -25,16 +26,17 @@ } }; - const handleSave = async (config: Partial) => { + const handleSave = async (update: Partial) => { try { - const result = await api.systemConfigApi.updateConfig({ + const { data: newConfig } = await api.systemConfigApi.updateConfig({ systemConfigDto: { ...savedConfig, - ...config, + ...update, }, }); - savedConfig = { ...result.data }; + config = cloneDeep(newConfig); + savedConfig = cloneDeep(newConfig); notificationController.show({ message: 'Settings saved', type: NotificationType.Info }); dispatch('save'); diff --git a/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte b/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte index 4539a6adea..bac314d66b 100644 --- a/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte +++ b/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte @@ -12,7 +12,8 @@ import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; import SettingSelect from '../setting-select.svelte'; import SettingSwitch from '../setting-switch.svelte'; - import { isEqual } from 'lodash-es'; + import SettingCheckboxes from '../setting-checkboxes.svelte'; + import { isEqual, sortBy } from 'lodash-es'; import { fade } from 'svelte/transition'; import SettingAccordion from '../setting-accordion.svelte'; import { mdiHelpCircleOutline } from '@mdi/js'; @@ -89,6 +90,21 @@ ]} name="acodec" isEdited={config.ffmpeg.targetAudioCodec !== savedConfig.ffmpeg.targetAudioCodec} + on:select={() => (config.ffmpeg.acceptedAudioCodecs = [config.ffmpeg.targetAudioCodec])} + /> + + (config.ffmpeg.acceptedVideoCodecs = [config.ffmpeg.targetVideoCodec])} + /> + + + import { quintOut } from 'svelte/easing'; + import { fly } from 'svelte/transition'; + + export let value: string[]; + export let options: { value: string; text: string }[]; + export let label = ''; + export let desc = ''; + export let name = ''; + export let isEdited = false; + export let disabled = false; + + function handleCheckboxChange(option: string) { + if (value.includes(option)) { + value = value.filter((item) => item !== option); + } else { + value = [...value, option]; + } + } + + +
+
+ + + {#if isEdited} +
+ Unsaved change +
+ {/if} +
+ + {#if desc} +

+ {desc} +

+ {/if} + + {#each options as option} + + {/each} +
diff --git a/web/src/lib/components/admin-page/settings/setting-select.svelte b/web/src/lib/components/admin-page/settings/setting-select.svelte index cafa053932..5b080c2328 100644 --- a/web/src/lib/components/admin-page/settings/setting-select.svelte +++ b/web/src/lib/components/admin-page/settings/setting-select.svelte @@ -1,6 +1,7 @@