Option to transcode surround sound audio to AC3

This commit is contained in:
Damontecres 2026-02-21 14:29:33 -05:00
parent 55b148971e
commit eecdea92e0
No known key found for this signature in database
7 changed files with 97 additions and 6 deletions

View file

@ -52,7 +52,10 @@ sealed interface AppPreference<Pref, T> {
value: T?, value: T?,
): String? = null ): String? = null
fun validate(value: T): PreferenceValidation = PreferenceValidation.Valid fun validate(
prefs: Pref,
value: T,
): PreferenceValidation = PreferenceValidation.Valid
companion object { companion object {
val SkipForward = val SkipForward =
@ -414,18 +417,49 @@ sealed interface AppPreference<Pref, T> {
defaultValue = true, defaultValue = true,
getter = { it.playbackPreferences.overrides.ac3Supported }, getter = { it.playbackPreferences.overrides.ac3Supported },
setter = { prefs, value -> setter = { prefs, value ->
prefs.updatePlaybackOverrides { ac3Supported = value } prefs.updatePlaybackOverrides {
ac3Supported = value
if (!value) preferAc3Surround = false
}
}, },
summaryOn = R.string.enabled, summaryOn = R.string.enabled,
summaryOff = R.string.disabled, summaryOff = R.string.disabled,
) )
val PreferAc3ForSurround =
AppSwitchPreference<AppPreferences>(
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 = val DownMixStereo =
AppSwitchPreference<AppPreferences>( AppSwitchPreference<AppPreferences>(
title = R.string.downmix_stereo, title = R.string.downmix_stereo,
defaultValue = false, defaultValue = false,
getter = { it.playbackPreferences.overrides.downmixStereo }, getter = { it.playbackPreferences.overrides.downmixStereo },
setter = { prefs, value -> setter = { prefs, value ->
prefs.updatePlaybackOverrides { downmixStereo = value } prefs.updatePlaybackOverrides {
downmixStereo = value
if (value) preferAc3Surround = false
}
}, },
summaryOn = R.string.enabled, summaryOn = R.string.enabled,
summaryOff = R.string.disabled, summaryOff = R.string.disabled,
@ -1066,6 +1100,7 @@ private val ExoPlayerSettings =
AppPreference.FfmpegPreference, AppPreference.FfmpegPreference,
AppPreference.DownMixStereo, AppPreference.DownMixStereo,
AppPreference.Ac3Supported, AppPreference.Ac3Supported,
AppPreference.PreferAc3ForSurround,
AppPreference.DirectPlayAss, AppPreference.DirectPlayAss,
AppPreference.DirectPlayPgs, AppPreference.DirectPlayPgs,
AppPreference.DirectPlayDoviProfile7, AppPreference.DirectPlayDoviProfile7,
@ -1206,11 +1241,16 @@ data class AppSwitchPreference<Pref>(
override val defaultValue: Boolean, override val defaultValue: Boolean,
override val getter: (prefs: Pref) -> Boolean, override val getter: (prefs: Pref) -> Boolean,
override val setter: (prefs: Pref, value: Boolean) -> Pref, 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 summary: Int? = null,
@param:StringRes val summaryOn: Int? = null, @param:StringRes val summaryOn: Int? = null,
@param:StringRes val summaryOff: Int? = null, @param:StringRes val summaryOff: Int? = null,
) : AppPreference<Pref, Boolean> { ) : AppPreference<Pref, Boolean> {
override fun validate(
prefs: Pref,
value: Boolean,
): PreferenceValidation = validator.invoke(prefs, value)
override fun summary( override fun summary(
context: Context, context: Context,
value: Boolean?, value: Boolean?,

View file

@ -44,6 +44,7 @@ class DeviceProfileService
pgsDirectPlay = prefs.overrides.directPlayPgs, pgsDirectPlay = prefs.overrides.directPlayPgs,
dolbyVisionELDirectPlay = prefs.overrides.directPlayDolbyVisionEL, dolbyVisionELDirectPlay = prefs.overrides.directPlayDolbyVisionEL,
decodeAv1 = prefs.overrides.decodeAv1, decodeAv1 = prefs.overrides.decodeAv1,
preferAc3ForSurround = prefs.overrides.preferAc3Surround,
jellyfinTenEleven = jellyfinTenEleven =
serverVersion != null && serverVersion >= ServerVersion(10, 11, 0), serverVersion != null && serverVersion >= ServerVersion(10, 11, 0),
) )
@ -59,6 +60,7 @@ class DeviceProfileService
pgsDirectPlay = newConfig.pgsDirectPlay, pgsDirectPlay = newConfig.pgsDirectPlay,
dolbyVisionELDirectPlay = newConfig.dolbyVisionELDirectPlay, dolbyVisionELDirectPlay = newConfig.dolbyVisionELDirectPlay,
decodeAv1 = prefs.overrides.decodeAv1, decodeAv1 = prefs.overrides.decodeAv1,
preferAc3ForSurround = prefs.overrides.preferAc3Surround,
jellyfinTenEleven = newConfig.jellyfinTenEleven, jellyfinTenEleven = newConfig.jellyfinTenEleven,
) )
} }
@ -78,5 +80,6 @@ data class DeviceProfileConfiguration(
val pgsDirectPlay: Boolean, val pgsDirectPlay: Boolean,
val dolbyVisionELDirectPlay: Boolean, val dolbyVisionELDirectPlay: Boolean,
val decodeAv1: Boolean, val decodeAv1: Boolean,
val preferAc3ForSurround: Boolean,
val jellyfinTenEleven: Boolean, val jellyfinTenEleven: Boolean,
) )

View file

@ -441,7 +441,7 @@ fun PreferencesContent(
value = value, value = value,
onNavigate = viewModel.navigationManager::navigateTo, onNavigate = viewModel.navigationManager::navigateTo,
onValueChange = { newValue -> onValueChange = { newValue ->
val validation = pref.validate(newValue) val validation = pref.validate(preferences, newValue)
when (validation) { when (validation) {
is PreferenceValidation.Invalid -> { is PreferenceValidation.Invalid -> {
// TODO? // TODO?

View file

@ -153,7 +153,7 @@ fun SubtitlePreferencesContent(
value = value, value = value,
onNavigate = viewModel.navigationManager::navigateTo, onNavigate = viewModel.navigationManager::navigateTo,
onValueChange = { newValue -> onValueChange = { newValue ->
val validation = pref.validate(newValue) val validation = pref.validate(preferences, newValue)
when (validation) { when (validation) {
is PreferenceValidation.Invalid -> { is PreferenceValidation.Invalid -> {
// TODO? // TODO?

View file

@ -71,6 +71,7 @@ fun createDeviceProfile(
dolbyVisionELDirectPlay: Boolean, dolbyVisionELDirectPlay: Boolean,
decodeAv1: Boolean, decodeAv1: Boolean,
jellyfinTenEleven: Boolean, jellyfinTenEleven: Boolean,
preferAc3ForSurround: Boolean,
) = buildDeviceProfile { ) = buildDeviceProfile {
val allowedAudioCodecs = val allowedAudioCodecs =
when { when {
@ -132,6 +133,23 @@ fun createDeviceProfile(
// / Transcoding profiles // / Transcoding profiles
// Video // 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 { transcodingProfile {
type = DlnaProfileType.VIDEO type = DlnaProfileType.VIDEO
context = EncodingContext.STREAMING 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 // Audio channel profile
codecProfile { codecProfile {
type = CodecType.VIDEO_AUDIO type = CodecType.VIDEO_AUDIO

View file

@ -48,6 +48,7 @@ message PlaybackOverrides{
MediaExtensionStatus media_extensions_enabled = 5; MediaExtensionStatus media_extensions_enabled = 5;
bool direct_play_dolby_vision_e_l = 6; bool direct_play_dolby_vision_e_l = 6;
bool decode_av1 = 7; bool decode_av1 = 7;
bool prefer_ac3_surround = 8;
} }
message PlaybackPreferences { message PlaybackPreferences {

View file

@ -522,6 +522,8 @@
<string name="display_presets">Display presets</string> <string name="display_presets">Display presets</string>
<string name="display_presets_description">Built-in presets to quickly style all rows</string> <string name="display_presets_description">Built-in presets to quickly style all rows</string>
<string name="customize_home_summary">Choose rows and images on the home page</string> <string name="customize_home_summary">Choose rows and images on the home page</string>
<string name="prefer_ac3_for_surround_summary"><![CDATA[Transcode 2+ channel audio to AC3/Dolby Digital]]></string>
<string name="prefer_ac3_for_surround">Use AC3 for surround sound audio</string>
<string-array name="theme_song_volume"> <string-array name="theme_song_volume">
<item>Disabled</item> <item>Disabled</item>