From 296d7b92268319e33efee4ee64a4de3e7a912ae7 Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Fri, 28 Nov 2025 15:57:11 -0500 Subject: [PATCH] Setting for subtitle margin & fix subtitle opacity settings (#338) Adds a subtitle style option to set the margin below unstyled subtitles. This is roughly the percent of the screen below the subtitles. It applies to both ExoPlayer & MPV. Also fixes font & background opacity not being applied in some cases. Fixes #225 Closes #178 --- .../preferences/AppPreferencesSerializer.kt | 1 + .../wholphin/services/AppUpgradeHandler.kt | 17 +++- .../wholphin/ui/playback/PlaybackPage.kt | 5 +- .../ui/preferences/PreferencesContent.kt | 2 + .../preferences/subtitle/SubtitleSettings.kt | 53 ++++++++-- .../preferences/subtitle/SubtitleStylePage.kt | 99 ++++++++++++------- app/src/main/proto/WholphinDataStore.proto | 1 + app/src/main/res/values/strings.xml | 1 + 8 files changed, 130 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt index a4c38f66..b60a5eca 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt @@ -152,4 +152,5 @@ fun SubtitlePreferences.Builder.resetSubtitles() { backgroundColor = SubtitleSettings.BackgroundColor.defaultValue.toArgb() backgroundOpacity = SubtitleSettings.BackgroundOpacity.defaultValue.toInt() backgroundStyle = SubtitleSettings.BackgroundStylePref.defaultValue + margin = SubtitleSettings.Margin.defaultValue.toInt() } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt b/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt index ba7de582..87356945 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt @@ -10,7 +10,9 @@ import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.updateInterfacePreferences import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides +import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel +import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings import com.github.damontecres.wholphin.util.Version import dagger.hilt.android.qualifiers.ApplicationContext import timber.log.Timber @@ -97,7 +99,7 @@ suspend fun upgradeApp( current: Version, appPreferences: DataStore, ) { - if (previous.isEqualOrBefore(Version.Companion.fromString("0.1.0-2-g0"))) { + if (previous.isEqualOrBefore(Version.fromString("0.1.0-2-g0"))) { appPreferences.updateData { it.updatePlaybackOverrides { ac3Supported = AppPreference.Ac3Supported.defaultValue @@ -107,21 +109,28 @@ suspend fun upgradeApp( } } } - if (previous.isEqualOrBefore(Version.Companion.fromString("0.2.3-6-g0"))) { + if (previous.isEqualOrBefore(Version.fromString("0.2.3-6-g0"))) { appPreferences.updateData { it.updateInterfacePreferences { navDrawerSwitchOnFocus = AppPreference.NavDrawerSwitchOnFocus.defaultValue } } } - if (previous.isEqualOrBefore(Version.Companion.fromString("0.2.5-11-g0"))) { + if (previous.isEqualOrBefore(Version.fromString("0.2.5-11-g0"))) { appPreferences.updateData { it.updateInterfacePreferences { showClock = AppPreference.ShowClock.defaultValue } } } - if (previous.isEqualOrBefore(Version.Companion.fromString("0.2.7-1-g0"))) { + if (previous.isEqualOrBefore(Version.fromString("0.2.7-1-g0"))) { PreferencesViewModel.resetSubtitleSettings(appPreferences) } + if (previous.isEqualOrBefore(Version.fromString("0.3.2-4-g0"))) { + appPreferences.updateData { + it.updateSubtitlePreferences { + margin = SubtitleSettings.Margin.defaultValue.toInt() + } + } + } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt index 1600cc9f..2979b947 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.intl.Locale @@ -117,6 +118,7 @@ fun PlaybackPage( LoadingState.Success -> { val prefs = preferences.appPreferences.playbackPreferences val scope = rememberCoroutineScope() + val configuration = LocalConfiguration.current val density = LocalDensity.current val player = viewModel.player @@ -142,11 +144,11 @@ fun PlaybackPage( val subtitleSearchLanguage by viewModel.subtitleSearchLanguage.observeAsState(Locale.current.language) var playbackDialog by remember { mutableStateOf(null) } - OneTimeLaunchedEffect { if (prefs.playerBackend == PlayerBackend.MPV) { scope.launch(Dispatchers.Main + ExceptionHandler()) { preferences.appPreferences.interfacePreferences.subtitlesPreferences.applyToMpv( + configuration, density, ) } @@ -322,6 +324,7 @@ fun PlaybackPage( preferences.appPreferences.interfacePreferences.subtitlesPreferences.let { setStyle(it.toSubtitleStyle()) setFixedTextSize(Dimension.SP, it.fontSize.toFloat()) + setBottomPaddingFraction(it.margin.toFloat() / 100f) } } }, 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 1547f2e2..1095703f 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 @@ -69,6 +69,7 @@ fun PreferencesContent( modifier: Modifier = Modifier, viewModel: PreferencesViewModel = hiltViewModel(), updateVM: UpdateViewModel = hiltViewModel(), + onFocus: (Int, Int) -> Unit = { _, _ -> }, ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -216,6 +217,7 @@ fun PreferencesContent( if (focused) { focusedIndex = Pair(groupIndex, prefIndex) if (movementSounds) playOnClickSound(context) + onFocus.invoke(groupIndex, prefIndex) } } when (pref) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleSettings.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleSettings.kt index 88521068..bfc342cd 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleSettings.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleSettings.kt @@ -1,10 +1,12 @@ package com.github.damontecres.wholphin.ui.preferences.subtitle +import android.content.res.Configuration import android.graphics.Typeface import androidx.annotation.OptIn import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.media3.common.util.UnstableApi import androidx.media3.ui.CaptionStyleCompat @@ -21,6 +23,7 @@ import com.github.damontecres.wholphin.ui.indexOfFirstOrNull import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup import com.github.damontecres.wholphin.util.mpv.MPVLib import com.github.damontecres.wholphin.util.mpv.setPropertyColor +import timber.log.Timber object SubtitleSettings { val FontSize = @@ -183,6 +186,23 @@ object SubtitleSettings { valueToIndex = { it.number }, ) + val Margin = + AppSliderPreference( + title = R.string.subtitle_margin, + defaultValue = 8, + min = 0, + max = 100, + interval = 1, + getter = { + it.interfacePreferences.subtitlesPreferences.margin + .toLong() + }, + setter = { prefs, value -> + prefs.updateSubtitlePreferences { margin = value.toInt() } + }, + summarizer = { value -> value?.let { "$it%" } }, + ) + val Reset = AppClickablePreference( title = R.string.reset, @@ -223,16 +243,23 @@ object SubtitleSettings { PreferenceGroup( title = R.string.more, preferences = - listOf(Reset), + listOf( + Margin, + Reset, + ), ), ) + private fun combine( + color: Int, + opacity: Int, + ) = ((opacity / 100.0 * 255).toInt().shl(24)).or(color.and(0x00FFFFFF)) + @OptIn(UnstableApi::class) fun SubtitlePreferences.toSubtitleStyle(): CaptionStyleCompat { - val fo = (fontOpacity / 100.0 * 255).toInt().shl(24) - val bg = (backgroundOpacity / 100.0 * 255).toInt().shl(24).or(backgroundColor) + val bg = combine(backgroundColor, backgroundOpacity) return CaptionStyleCompat( - fo.or(fontColor), + combine(fontColor, fontOpacity), if (backgroundStyle == BackgroundStyle.BG_WRAP)bg else 0, if (backgroundStyle == BackgroundStyle.BG_BOXED) bg else 0, when (edgeStyle) { @@ -240,7 +267,7 @@ object SubtitleSettings { EdgeStyle.EDGE_SOLID -> CaptionStyleCompat.EDGE_TYPE_OUTLINE EdgeStyle.EDGE_SHADOW -> CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW }, - fo.or(edgeColor), + combine(edgeColor, fontOpacity), when { fontBold && fontItalic -> Typeface.defaultFromStyle(Typeface.BOLD_ITALIC) fontBold -> Typeface.defaultFromStyle(Typeface.BOLD) @@ -250,11 +277,14 @@ object SubtitleSettings { ) } - fun SubtitlePreferences.applyToMpv(density: Density) { + fun SubtitlePreferences.applyToMpv( + configuration: Configuration, + density: Density, + ) { val fo = (fontOpacity / 100.0 * 255).toInt().shl(24) - val fc = Color(fo.or(fontColor)) - val bg = Color((backgroundOpacity / 100.0 * 255).toInt().shl(24).or(backgroundColor)) - val edge = Color(fo.or(edgeColor)) + val fc = Color(combine(fontColor, fontOpacity)) + val bg = Color(combine(backgroundColor, backgroundOpacity)) + val edge = Color(combine(edgeColor, fontOpacity)) // TODO weird, but seems to get the size to be very close to matching sizes between renderers val fontSizePx = with(density) { fontSize.sp.toPx() * .8 }.toInt() @@ -262,6 +292,11 @@ object SubtitleSettings { MPVLib.setPropertyColor("sub-color", fc) MPVLib.setPropertyColor("sub-outline-color", edge) + val heightInPx = with(density) { configuration.screenHeightDp.dp.toPx() } + val margin = (heightInPx * (margin.toFloat() / 100f) * .8).toInt() + MPVLib.setPropertyInt("sub-margin-y", margin) + Timber.d("MPV subtitles: fontSizePx=%s, margin=$margin", fontSizePx, margin) + when (edgeStyle) { EdgeStyle.EDGE_NONE, EdgeStyle.UNRECOGNIZED, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleStylePage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleStylePage.kt index 6a33710c..e53e1cfe 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleStylePage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleStylePage.kt @@ -33,7 +33,6 @@ import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption import com.github.damontecres.wholphin.ui.preferences.PreferencesContent import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.toSubtitleStyle -import timber.log.Timber @OptIn(UnstableApi::class) @Composable @@ -48,8 +47,9 @@ fun SubtitleStylePage( preferences = it } } - Timber.v("SubtitleStylePage") val prefs = preferences.interfacePreferences.subtitlesPreferences + var focusedOnMargin by remember { mutableStateOf(false) } + Row( modifier = modifier, ) { @@ -68,45 +68,74 @@ fun SubtitleStylePage( Modifier .fillMaxSize(), ) - Column( - verticalArrangement = Arrangement.SpaceBetween, - modifier = - Modifier - .padding(16.dp) - .fillMaxSize(), - ) { - val examples = - mapOf( - "Subtitles will look like this" to 48.dp, - "This is another example" to 24.dp, - "Longer multi line subtitles will\nlook like this" to 0.dp, - ) - examples.forEach { (text, padding) -> - AndroidView( - factory = { context -> - SubtitleView(context) - }, - update = { - it.setStyle(prefs.toSubtitleStyle()) - it.setFixedTextSize(Dimension.SP, prefs.fontSize.toFloat()) - it.setCues( - listOf( - Cue.Builder().setText(text).build(), - ), - ) - }, - modifier = - Modifier - .weight(1f) - .fillMaxWidth() - .padding(bottom = padding), - ) + if (!focusedOnMargin) { + Column( + verticalArrangement = Arrangement.SpaceBetween, + modifier = + Modifier + .padding(16.dp) + .fillMaxSize(), + ) { + val examples = + mapOf( + "Subtitles will look like this" to 48.dp, + "This is another example" to 24.dp, + "Longer multi line subtitles will\nlook like this" to 0.dp, + ) + examples.forEach { (text, padding) -> + AndroidView( + factory = { context -> + SubtitleView(context) + }, + update = { + it.setStyle(prefs.toSubtitleStyle()) + it.setFixedTextSize(Dimension.SP, prefs.fontSize.toFloat()) + it.setCues( + listOf( + Cue.Builder().setText(text).build(), + ), + ) + }, + modifier = + Modifier + .weight(1f) + .fillMaxWidth() + .padding(bottom = padding), + ) + } } + } else { + // Margin + AndroidView( + factory = { context -> + SubtitleView(context) + }, + update = { + it.setStyle(prefs.toSubtitleStyle()) + it.setFixedTextSize(Dimension.SP, prefs.fontSize.toFloat()) + it.setCues( + listOf( + Cue.Builder().setText("Subtitles margin below here").build(), + ), + ) + it.setBottomPaddingFraction(prefs.margin.toFloat() / 100f) + }, + modifier = + Modifier + .fillMaxSize(), + ) } } PreferencesContent( initialPreferences = preferences, preferenceScreenOption = PreferenceScreenOption.SUBTITLES, + onFocus = { groupIndex, prefIndex -> + + focusedOnMargin = + SubtitleSettings.preferences.getOrNull(groupIndex)?.preferences?.getOrNull( + prefIndex, + ) == SubtitleSettings.Margin + }, modifier = Modifier .fillMaxHeight() diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto index 50b097fd..f5da5dbf 100644 --- a/app/src/main/proto/WholphinDataStore.proto +++ b/app/src/main/proto/WholphinDataStore.proto @@ -119,6 +119,7 @@ message SubtitlePreferences{ BackgroundStyle background_style = 8; bool font_bold = 9; bool font_italic = 10; + int32 margin = 11; } message InterfacePreferences { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5c92e74e..49df4fca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -326,6 +326,7 @@ Remove Dolby Vision Dolby Atmos + Margin Disabled