From eecdea92e095e1ff8128c2c83f62aa298e21f685 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Sat, 21 Feb 2026 14:29:33 -0500 Subject: [PATCH] Option to transcode surround sound audio to AC3 --- .../wholphin/preferences/AppPreference.kt | 48 +++++++++++++++++-- .../wholphin/services/DeviceProfileService.kt | 3 ++ .../ui/preferences/PreferencesContent.kt | 2 +- .../subtitle/SubtitlePreferencesContent.kt | 2 +- .../util/profile/DeviceProfileUtils.kt | 45 +++++++++++++++++ app/src/main/proto/WholphinDataStore.proto | 1 + app/src/main/res/values/strings.xml | 2 + 7 files changed, 97 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index 709abd6c..84e91c95 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -52,7 +52,10 @@ sealed interface AppPreference { value: T?, ): String? = null - fun validate(value: T): PreferenceValidation = PreferenceValidation.Valid + fun validate( + prefs: Pref, + value: T, + ): PreferenceValidation = PreferenceValidation.Valid companion object { val SkipForward = @@ -414,18 +417,49 @@ sealed interface AppPreference { defaultValue = true, getter = { it.playbackPreferences.overrides.ac3Supported }, setter = { prefs, value -> - prefs.updatePlaybackOverrides { ac3Supported = value } + prefs.updatePlaybackOverrides { + ac3Supported = value + if (!value) preferAc3Surround = false + } }, summaryOn = R.string.enabled, summaryOff = R.string.disabled, ) + val PreferAc3ForSurround = + AppSwitchPreference( + title = R.string.prefer_ac3_for_surround, + defaultValue = true, + getter = { it.playbackPreferences.overrides.preferAc3Surround }, + setter = { prefs, value -> + prefs.updatePlaybackOverrides { + preferAc3Surround = value + } + }, + summaryOn = R.string.prefer_ac3_for_surround_summary, +// summaryOn = R.string.enabled, + summaryOff = R.string.disabled, + validator = { prefs, value -> + prefs.playbackPreferences.overrides.let { + if (value && !it.ac3Supported) { + PreferenceValidation.Invalid("AC3 support is not enabled") + } else if (value && it.downmixStereo) { + PreferenceValidation.Invalid("Always downmixing to stereo") + } else { + PreferenceValidation.Valid + } + } + }, + ) val DownMixStereo = AppSwitchPreference( title = R.string.downmix_stereo, defaultValue = false, getter = { it.playbackPreferences.overrides.downmixStereo }, setter = { prefs, value -> - prefs.updatePlaybackOverrides { downmixStereo = value } + prefs.updatePlaybackOverrides { + downmixStereo = value + if (value) preferAc3Surround = false + } }, summaryOn = R.string.enabled, summaryOff = R.string.disabled, @@ -1066,6 +1100,7 @@ private val ExoPlayerSettings = AppPreference.FfmpegPreference, AppPreference.DownMixStereo, AppPreference.Ac3Supported, + AppPreference.PreferAc3ForSurround, AppPreference.DirectPlayAss, AppPreference.DirectPlayPgs, AppPreference.DirectPlayDoviProfile7, @@ -1206,11 +1241,16 @@ data class AppSwitchPreference( override val defaultValue: Boolean, override val getter: (prefs: Pref) -> Boolean, override val setter: (prefs: Pref, value: Boolean) -> Pref, - val validator: (value: Boolean) -> PreferenceValidation = { PreferenceValidation.Valid }, + val validator: (prefs: Pref, value: Boolean) -> PreferenceValidation = { _, _ -> PreferenceValidation.Valid }, @param:StringRes val summary: Int? = null, @param:StringRes val summaryOn: Int? = null, @param:StringRes val summaryOff: Int? = null, ) : AppPreference { + override fun validate( + prefs: Pref, + value: Boolean, + ): PreferenceValidation = validator.invoke(prefs, value) + override fun summary( context: Context, value: Boolean?, diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt index 976fb2c8..dd037159 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt @@ -44,6 +44,7 @@ class DeviceProfileService pgsDirectPlay = prefs.overrides.directPlayPgs, dolbyVisionELDirectPlay = prefs.overrides.directPlayDolbyVisionEL, decodeAv1 = prefs.overrides.decodeAv1, + preferAc3ForSurround = prefs.overrides.preferAc3Surround, jellyfinTenEleven = serverVersion != null && serverVersion >= ServerVersion(10, 11, 0), ) @@ -59,6 +60,7 @@ class DeviceProfileService pgsDirectPlay = newConfig.pgsDirectPlay, dolbyVisionELDirectPlay = newConfig.dolbyVisionELDirectPlay, decodeAv1 = prefs.overrides.decodeAv1, + preferAc3ForSurround = prefs.overrides.preferAc3Surround, jellyfinTenEleven = newConfig.jellyfinTenEleven, ) } @@ -78,5 +80,6 @@ data class DeviceProfileConfiguration( val pgsDirectPlay: Boolean, val dolbyVisionELDirectPlay: Boolean, val decodeAv1: Boolean, + val preferAc3ForSurround: Boolean, val jellyfinTenEleven: Boolean, ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt index b04293f8..47d7fbe2 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt @@ -441,7 +441,7 @@ fun PreferencesContent( value = value, onNavigate = viewModel.navigationManager::navigateTo, onValueChange = { newValue -> - val validation = pref.validate(newValue) + val validation = pref.validate(preferences, newValue) when (validation) { is PreferenceValidation.Invalid -> { // TODO? diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitlePreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitlePreferencesContent.kt index 8abc6dbe..4070476c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitlePreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitlePreferencesContent.kt @@ -153,7 +153,7 @@ fun SubtitlePreferencesContent( value = value, onNavigate = viewModel.navigationManager::navigateTo, onValueChange = { newValue -> - val validation = pref.validate(newValue) + val validation = pref.validate(preferences, newValue) when (validation) { is PreferenceValidation.Invalid -> { // TODO? diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt index 6272af5a..0fe160cf 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt @@ -71,6 +71,7 @@ fun createDeviceProfile( dolbyVisionELDirectPlay: Boolean, decodeAv1: Boolean, jellyfinTenEleven: Boolean, + preferAc3ForSurround: Boolean, ) = buildDeviceProfile { val allowedAudioCodecs = when { @@ -132,6 +133,23 @@ fun createDeviceProfile( // / Transcoding profiles // Video + if (preferAc3ForSurround) { + transcodingProfile { + type = DlnaProfileType.VIDEO + context = EncodingContext.STREAMING + + container = Codec.Container.TS + protocol = MediaStreamProtocol.HLS + + if (supportsHevc) videoCodec(Codec.Video.HEVC) + videoCodec(Codec.Video.H264) + + audioCodec(Codec.Audio.AC3) + + copyTimestamps = false + enableSubtitlesInManifest = true + } + } transcodingProfile { type = DlnaProfileType.VIDEO context = EncodingContext.STREAMING @@ -516,6 +534,33 @@ fun createDeviceProfile( } } + if (preferAc3ForSurround) { + codecProfile { + type = CodecType.VIDEO_AUDIO + codec = Codec.Audio.AAC + + conditions { + ProfileConditionValue.AUDIO_PROFILE equals "none" + } + + applyConditions { + ProfileConditionValue.AUDIO_CHANNELS greaterThanOrEquals 3 + } + } + codecProfile { + type = CodecType.VIDEO_AUDIO + codec = Codec.Audio.OPUS + + conditions { + ProfileConditionValue.AUDIO_PROFILE equals "none" + } + + applyConditions { + ProfileConditionValue.AUDIO_CHANNELS greaterThanOrEquals 3 + } + } + } + // Audio channel profile codecProfile { type = CodecType.VIDEO_AUDIO diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto index 737dbb57..134b2341 100644 --- a/app/src/main/proto/WholphinDataStore.proto +++ b/app/src/main/proto/WholphinDataStore.proto @@ -48,6 +48,7 @@ message PlaybackOverrides{ MediaExtensionStatus media_extensions_enabled = 5; bool direct_play_dolby_vision_e_l = 6; bool decode_av1 = 7; + bool prefer_ac3_surround = 8; } message PlaybackPreferences { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c4f3db4f..d966e472 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -522,6 +522,8 @@ Display presets Built-in presets to quickly style all rows Choose rows and images on the home page + + Use AC3 for surround sound audio Disabled