From 36162d6fc553aa8183bab0fbc17c596b6afff8f8 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:16:12 -0500 Subject: [PATCH 01/45] Revert "Integrate with libass-android to support SSA/ASS subtitles in ExoPlayer" (#1051) Unfortunately, there seem to be issues with switching to `libass-android`, so reverting those changes so I can cut a release and troubleshoot later. Reverts damontecres/Wholphin#569 --- app/build.gradle.kts | 1 - .../wholphin/services/PlayerFactory.kt | 115 +-- .../wholphin/ui/playback/PlaybackPage.kt | 55 +- .../wholphin/ui/playback/PlaybackViewModel.kt | 10 +- .../wholphin/ui/slideshow/SlideshowPage.kt | 789 +++++++++--------- .../ui/slideshow/SlideshowViewModel.kt | 114 ++- app/src/main/res/values/strings.xml | 2 +- gradle/libs.versions.toml | 3 - 8 files changed, 514 insertions(+), 575 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b5f86522..22bd0deb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -231,7 +231,6 @@ dependencies { implementation(libs.androidx.media3.exoplayer.dash) implementation(libs.androidx.media3.ui) implementation(libs.androidx.media3.ui.compose) - implementation(libs.ass.media) implementation(libs.coil.core) implementation(libs.coil.compose) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt b/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt index a5d6fc78..425ab94d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt @@ -10,28 +10,22 @@ import androidx.datastore.core.DataStore import androidx.media3.common.C import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi -import androidx.media3.datasource.DefaultDataSource import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.Renderer -import androidx.media3.exoplayer.RenderersFactory import androidx.media3.exoplayer.mediacodec.MediaCodecSelector -import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.video.MediaCodecVideoRenderer import androidx.media3.exoplayer.video.VideoRendererEventListener -import androidx.media3.extractor.DefaultExtractorsFactory +import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.MediaExtensionStatus import com.github.damontecres.wholphin.preferences.PlaybackPreferences import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.util.mpv.MpvPlayer import dagger.hilt.android.qualifiers.ApplicationContext -import io.github.peerless2012.ass.media.AssHandler -import io.github.peerless2012.ass.media.factory.AssRenderersFactory -import io.github.peerless2012.ass.media.kt.withAssMkvSupport -import io.github.peerless2012.ass.media.parser.AssSubtitleParserFactory -import io.github.peerless2012.ass.media.type.AssRenderType import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import timber.log.Timber import java.lang.reflect.Constructor @@ -52,17 +46,71 @@ class PlayerFactory var currentPlayer: Player? = null private set + fun createVideoPlayer(): Player { + if (currentPlayer?.isReleased == false) { + Timber.w("Player was not released before trying to create a new one!") + currentPlayer?.release() + } + + val prefs = runBlocking { appPreferences.data.firstOrNull()?.playbackPreferences } + val backend = prefs?.playerBackend ?: AppPreference.PlayerBackendPref.defaultValue + val newPlayer = + when (backend) { + PlayerBackend.PREFER_MPV, + PlayerBackend.MPV, + -> { + val enableHardwareDecoding = + prefs?.mpvOptions?.enableHardwareDecoding + ?: AppPreference.MpvHardwareDecoding.defaultValue + val useGpuNext = + prefs?.mpvOptions?.useGpuNext + ?: AppPreference.MpvGpuNext.defaultValue + MpvPlayer(context, enableHardwareDecoding, useGpuNext) + .apply { + playWhenReady = true + } + } + + PlayerBackend.EXO_PLAYER, + PlayerBackend.UNRECOGNIZED, + -> { + val extensions = prefs?.overrides?.mediaExtensionsEnabled + val decodeAv1 = prefs?.overrides?.decodeAv1 == true + Timber.v("extensions=$extensions") + val rendererMode = + when (extensions) { + MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON + MediaExtensionStatus.MES_PREFERRED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF + else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON + } + ExoPlayer + .Builder(context) + .setRenderersFactory( + WholphinRenderersFactory(context, decodeAv1) + .setEnableDecoderFallback(true) + .setExtensionRendererMode(rendererMode), + ).build() + .apply { + playWhenReady = true + } + } + } + currentPlayer = newPlayer + return newPlayer + } + suspend fun createVideoPlayer( backend: PlayerBackend, prefs: PlaybackPreferences, - ): PlayerCreation { + ): Player { withContext(Dispatchers.Main) { if (currentPlayer?.isReleased == false) { Timber.w("Player was not released before trying to create a new one!") currentPlayer?.release() } } - var assHandler: AssHandler? = null + val newPlayer = when (backend) { PlayerBackend.PREFER_MPV, @@ -77,9 +125,8 @@ class PlayerFactory PlayerBackend.UNRECOGNIZED, -> { val extensions = prefs.overrides.mediaExtensionsEnabled - val directPlayAss = prefs.overrides.directPlayAss val decodeAv1 = prefs.overrides.decodeAv1 - Timber.v("extensions=$extensions, directPlayAss=$directPlayAss") + Timber.v("extensions=$extensions") val rendererMode = when (extensions) { MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON @@ -87,42 +134,17 @@ class PlayerFactory MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON } - val dataSourceFactory = DefaultDataSource.Factory(context) - val extractorsFactory = DefaultExtractorsFactory() - var renderersFactory: RenderersFactory = - WholphinRenderersFactory(context, decodeAv1) - .setEnableDecoderFallback(true) - .setExtensionRendererMode(rendererMode) - val mediaSourceFactory = - if (directPlayAss) { - assHandler = AssHandler(AssRenderType.OVERLAY_OPEN_GL) - val assSubtitleParserFactory = AssSubtitleParserFactory(assHandler) - renderersFactory = AssRenderersFactory(assHandler, renderersFactory) - DefaultMediaSourceFactory( - dataSourceFactory, - extractorsFactory.withAssMkvSupport( - assSubtitleParserFactory, - assHandler, - ), - ).setSubtitleParserFactory(assSubtitleParserFactory) - } else { - DefaultMediaSourceFactory( - dataSourceFactory, - extractorsFactory, - ) - } ExoPlayer .Builder(context) - .setMediaSourceFactory(mediaSourceFactory) - .setRenderersFactory(renderersFactory) - .build() - .apply { - assHandler?.init(this) - } + .setRenderersFactory( + WholphinRenderersFactory(context, decodeAv1) + .setEnableDecoderFallback(true) + .setExtensionRendererMode(rendererMode), + ).build() } } currentPlayer = newPlayer - return PlayerCreation(newPlayer, assHandler) + return newPlayer } } @@ -135,11 +157,6 @@ val Player.isReleased: Boolean } } -data class PlayerCreation( - val player: Player, - val assHandler: AssHandler? = null, -) - // Code is adapted from https://github.com/androidx/media/blob/release/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java#L436 class WholphinRenderersFactory( context: Context, 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 c7a4bf10..3b9958b6 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 @@ -1,8 +1,5 @@ package com.github.damontecres.wholphin.ui.playback -import android.view.Gravity -import android.view.ViewGroup -import android.widget.FrameLayout import androidx.activity.compose.BackHandler import androidx.annotation.Dimension import androidx.annotation.OptIn @@ -45,18 +42,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import androidx.core.view.children import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleStartEffect +import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.ui.SubtitleView import androidx.media3.ui.compose.PlayerSurface @@ -88,7 +83,6 @@ import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.Media3SubtitleOverride import com.github.damontecres.wholphin.util.mpv.MpvPlayer -import io.github.peerless2012.ass.media.widget.AssSubtitleView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -133,7 +127,8 @@ fun PlaybackPage( LoadingState.Success -> { val playerState by viewModel.currentPlayer.collectAsState() PlaybackPageContent( - playerState = playerState!!, + player = playerState!!.player, + playerBackend = playerState!!.backend, preferences = preferences, destination = destination, viewModel = viewModel, @@ -146,15 +141,13 @@ fun PlaybackPage( @OptIn(UnstableApi::class) @Composable fun PlaybackPageContent( - playerState: PlayerState, + player: Player, + playerBackend: PlayerBackend, preferences: UserPreferences, destination: Destination, modifier: Modifier = Modifier, viewModel: PlaybackViewModel, ) { - val player = playerState.player - val playerBackend = playerState.backend - val prefs = preferences.appPreferences.playbackPreferences val scope = rememberCoroutineScope() val configuration = LocalConfiguration.current @@ -327,14 +320,10 @@ fun PlaybackPageContent( .focusRequester(focusRequester) .focusable(), ) { - var playerSurfaceSize by remember { mutableStateOf(IntSize.Zero) } PlayerSurface( player = player, surfaceType = SURFACE_TYPE_SURFACE_VIEW, - modifier = - scaledModifier.onSizeChanged { - playerSurfaceSize = it - }, + modifier = scaledModifier, ) if (presentationState.coverSurface) { Box( @@ -427,7 +416,7 @@ fun PlaybackPageContent( remember(subtitleSettings) { subtitleSettings.imageSubtitleOpacity / 100f } // Subtitles - if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled && !presentationState.coverSurface) { + if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled) { val maxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f) val isImageSubtitles = remember(cues) { cues.firstOrNull()?.bitmap != null } AndroidView( @@ -438,42 +427,12 @@ fun PlaybackPageContent( setFixedTextSize(Dimension.SP, it.fontSize.toFloat()) setBottomPaddingFraction(it.margin.toFloat() / 100f) } - playerState.assHandler?.let { assHandler -> - if (prefs.overrides.directPlayAss) { - Timber.v("Adding AssSubtitleView") - addView( - AssSubtitleView(context, assHandler).apply { - layoutParams = - FrameLayout - .LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - ).apply { gravity = Gravity.CENTER } - }, - ) - } - } } }, update = { it.setCues(cues) Media3SubtitleOverride(subtitleSettings.calculateEdgeSize(density)) .apply(it) - it.children.firstOrNull { it is AssSubtitleView }?.let { - (it as? AssSubtitleView)?.apply { - val resized = - layoutParams.let { it.width != playerSurfaceSize.width || it.height != playerSurfaceSize.height } - if (resized) { - Timber.v("Resizing AssSubtitleView: $playerSurfaceSize") - layoutParams = - FrameLayout - .LayoutParams( - playerSurfaceSize.width, - playerSurfaceSize.height, - ).apply { gravity = Gravity.CENTER } - } - } - } }, onReset = { it.setCues(null) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index 4f652360..0d23f4bf 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -73,7 +73,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext -import io.github.peerless2012.ass.media.AssHandler import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -219,8 +218,7 @@ class PlaybackViewModel isHdr: Boolean, is4k: Boolean, ) { - val softwareDecoding = - !preferences.appPreferences.playbackPreferences.mpvOptions.enableHardwareDecoding + val softwareDecoding = !preferences.appPreferences.playbackPreferences.mpvOptions.enableHardwareDecoding val playerBackend = when (preferences.appPreferences.playbackPreferences.playerBackend) { PlayerBackend.UNRECOGNIZED, @@ -239,14 +237,13 @@ class PlaybackViewModel disconnectPlayer() } - val playerCreation = + player = playerFactory.createVideoPlayer( playerBackend, preferences.appPreferences.playbackPreferences, ) - this.player = playerCreation.player currentPlayer.update { - PlayerState(playerCreation.player, playerBackend, playerCreation.assHandler) + PlayerState(player, playerBackend) } configurePlayer() } @@ -1441,7 +1438,6 @@ class PlaybackViewModel data class PlayerState( val player: Player, val backend: PlayerBackend, - val assHandler: AssHandler?, ) data class MediaSegmentState( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowPage.kt index 3b57017e..f35bc102 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowPage.kt @@ -67,7 +67,6 @@ import com.github.damontecres.wholphin.ui.playback.isDirectionalDpad import com.github.damontecres.wholphin.ui.playback.isDpad import com.github.damontecres.wholphin.ui.playback.isEnterKey import com.github.damontecres.wholphin.ui.tryRequestFocus -import com.github.damontecres.wholphin.util.LoadingState import org.jellyfin.sdk.model.api.MediaType import timber.log.Timber import kotlin.math.abs @@ -89,429 +88,413 @@ fun SlideshowPage( ), ) { val context = LocalContext.current - val loading by viewModel.loading.collectAsState() - when (val st = loading) { - is LoadingState.Error -> { - ErrorMessage(st, modifier) + val loadingState by viewModel.loadingState.observeAsState(ImageLoadingState.Loading) + val imageFilter by viewModel.imageFilter.observeAsState(VideoFilter()) + val position by viewModel.position.observeAsState(0) + val pager by viewModel.pager.observeAsState() + val imageState by viewModel.image.observeAsState() + + var zoomFactor by rememberSaveable { mutableFloatStateOf(1f) } + val isZoomed = zoomFactor * 100 > 102 + var rotation by rememberSaveable { mutableFloatStateOf(0f) } + var showOverlay by rememberSaveable { mutableStateOf(false) } + var showFilterDialog by rememberSaveable { mutableStateOf(false) } + var panX by rememberSaveable { mutableFloatStateOf(0f) } + var panY by rememberSaveable { mutableFloatStateOf(0f) } + + val slideshowControls = + object : SlideshowControls { + override fun startSlideshow() { + showOverlay = false + viewModel.startSlideshow() + } + + override fun stopSlideshow() { + viewModel.stopSlideshow() + } } - LoadingState.Loading, - LoadingState.Pending, - -> { - LoadingPage(modifier) + val rotateAnimation: Float by animateFloatAsState( + targetValue = rotation, + label = "image_rotation", + ) + val zoomAnimation: Float by animateFloatAsState( + targetValue = zoomFactor, + label = "image_zoom", + ) + val panXAnimation: Float by animateFloatAsState( + targetValue = panX, + label = "image_panX", + ) + val panYAnimation: Float by animateFloatAsState( + targetValue = panY, + label = "image_panY", + ) + + val slideshowState by viewModel.slideshow.collectAsState() + val slideshowActive by viewModel.slideshowActive.collectAsState(false) + + val focusRequester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + focusRequester.tryRequestFocus() + } + + val density = LocalDensity.current + val screenHeight = LocalWindowInfo.current.containerSize.height + val screenWidth = LocalWindowInfo.current.containerSize.width + + val maxPanX = screenWidth * .75f + val maxPanY = screenHeight * .75f + + fun reset(resetRotate: Boolean) { + zoomFactor = 1f + panX = 0f + panY = 0f + if (resetRotate) rotation = 0f + } + + fun pan( + xFactor: Int, + yFactor: Int, + ) { + if (xFactor != 0) { + panX = (panX + with(density) { xFactor.dp.toPx() }).coerceIn(-maxPanX, maxPanX) } + if (yFactor != 0) { + panY = (panY + with(density) { yFactor.dp.toPx() }).coerceIn(-maxPanY, maxPanY) + } + } - LoadingState.Success -> { - val loadingState by viewModel.loadingState.observeAsState(ImageLoadingState.Loading) - val imageFilter by viewModel.imageFilter.observeAsState(VideoFilter()) - val position by viewModel.position.observeAsState(0) - val pager by viewModel.pager.observeAsState() - val imageState by viewModel.image.observeAsState() + fun zoom(factor: Float) { + if (factor < 0) { + val diffFactor = factor / (zoomFactor - 1f) + // zooming out + val panXDiff = abs(panX * diffFactor) + val panYDiff = abs(panY * diffFactor) + if (DEBUG) { + Timber.d( + "zoomFactor=$zoomFactor, factor=$factor, panX=$panX, panY=$panY, panXDiff=$panXDiff, panYDiff=$panYDiff", + ) + } + if (panX > 0f) { + panX -= panXDiff + } else if (panX < 0f) { + panX += panXDiff + } + if (panY > 0f) { + panY -= panYDiff + } else if (panY < 0f) { + panY += panYDiff + } + } + zoomFactor = (zoomFactor + factor).coerceIn(1f, 5f) + if (!isZoomed) { + // Always reset if not zoomed + panX = 0f + panY = 0f + } + } - var zoomFactor by rememberSaveable { mutableFloatStateOf(1f) } - val isZoomed = zoomFactor * 100 > 102 - var rotation by rememberSaveable { mutableFloatStateOf(0f) } - var showOverlay by rememberSaveable { mutableStateOf(false) } - var showFilterDialog by rememberSaveable { mutableStateOf(false) } - var panX by rememberSaveable { mutableFloatStateOf(0f) } - var panY by rememberSaveable { mutableFloatStateOf(0f) } + LaunchedEffect(imageState) { + reset(true) + } + val player = viewModel.player + val presentationState = rememberPresentationState(player) + LaunchedEffect(slideshowActive) { + player.repeatMode = + if (slideshowState.enabled) Player.REPEAT_MODE_OFF else Player.REPEAT_MODE_ONE + } - val slideshowControls = - object : SlideshowControls { - override fun startSlideshow() { - showOverlay = false - viewModel.startSlideshow() - } + var longPressing by remember { mutableStateOf(false) } - override fun stopSlideshow() { - viewModel.stopSlideshow() - } - } - - val rotateAnimation: Float by animateFloatAsState( - targetValue = rotation, - label = "image_rotation", - ) - val zoomAnimation: Float by animateFloatAsState( - targetValue = zoomFactor, - label = "image_zoom", - ) - val panXAnimation: Float by animateFloatAsState( - targetValue = panX, - label = "image_panX", - ) - val panYAnimation: Float by animateFloatAsState( - targetValue = panY, - label = "image_panY", + val contentModifier = + Modifier + .clickable( + interactionSource = null, + indication = null, + onClick = { + showOverlay = !showOverlay + }, ) - val slideshowState by viewModel.slideshow.collectAsState() - val slideshowActive by viewModel.slideshowActive.collectAsState(false) + Box( + modifier = + modifier + .background(Color.Black) + .focusRequester(focusRequester) + .focusable() + .onKeyEvent { + val isOverlayShowing = showOverlay || showFilterDialog + var result = false + if (!isOverlayShowing) { + if (longPressing && it.type == KeyEventType.KeyUp) { + // User stopped long pressing, so cancel the zooming action, but still consume the event so it doesn't move the image + longPressing = false + return@onKeyEvent true + } + longPressing = + it.nativeKeyEvent.isLongPress || + it.nativeKeyEvent.repeatCount > 0 + if (longPressing) { + when (it.key) { + Key.DirectionUp -> zoom(.05f) + Key.DirectionDown -> zoom(-.05f) - val focusRequester = remember { FocusRequester() } - - LaunchedEffect(Unit) { - focusRequester.tryRequestFocus() - } - - val density = LocalDensity.current - val screenHeight = LocalWindowInfo.current.containerSize.height - val screenWidth = LocalWindowInfo.current.containerSize.width - - val maxPanX = screenWidth * .75f - val maxPanY = screenHeight * .75f - - fun reset(resetRotate: Boolean) { - zoomFactor = 1f - panX = 0f - panY = 0f - if (resetRotate) rotation = 0f - } - - fun pan( - xFactor: Int, - yFactor: Int, - ) { - if (xFactor != 0) { - panX = (panX + with(density) { xFactor.dp.toPx() }).coerceIn(-maxPanX, maxPanX) - } - if (yFactor != 0) { - panY = (panY + with(density) { yFactor.dp.toPx() }).coerceIn(-maxPanY, maxPanY) - } - } - - fun zoom(factor: Float) { - if (factor < 0) { - val diffFactor = factor / (zoomFactor - 1f) - // zooming out - val panXDiff = abs(panX * diffFactor) - val panYDiff = abs(panY * diffFactor) - if (DEBUG) { - Timber.d( - "zoomFactor=$zoomFactor, factor=$factor, panX=$panX, panY=$panY, panXDiff=$panXDiff, panYDiff=$panYDiff", - ) - } - if (panX > 0f) { - panX -= panXDiff - } else if (panX < 0f) { - panX += panXDiff - } - if (panY > 0f) { - panY -= panYDiff - } else if (panY < 0f) { - panY += panYDiff - } - } - zoomFactor = (zoomFactor + factor).coerceIn(1f, 5f) - if (!isZoomed) { - // Always reset if not zoomed - panX = 0f - panY = 0f - } - } - - LaunchedEffect(imageState) { - reset(true) - } - val player = viewModel.player - val presentationState = rememberPresentationState(player) - LaunchedEffect(slideshowActive) { - player.repeatMode = - if (slideshowState.enabled) Player.REPEAT_MODE_OFF else Player.REPEAT_MODE_ONE - } - - var longPressing by remember { mutableStateOf(false) } - - val contentModifier = - Modifier - .clickable( - interactionSource = null, - indication = null, - onClick = { - showOverlay = !showOverlay - }, - ) - - Box( - modifier = - modifier - .background(Color.Black) - .focusRequester(focusRequester) - .focusable() - .onKeyEvent { - val isOverlayShowing = showOverlay || showFilterDialog - var result = false - if (!isOverlayShowing) { - if (longPressing && it.type == KeyEventType.KeyUp) { - // User stopped long pressing, so cancel the zooming action, but still consume the event so it doesn't move the image - longPressing = false - return@onKeyEvent true - } - longPressing = - it.nativeKeyEvent.isLongPress || - it.nativeKeyEvent.repeatCount > 0 - if (longPressing) { - when (it.key) { - Key.DirectionUp -> zoom(.05f) - Key.DirectionDown -> zoom(-.05f) - - // These work, but feel awkward because Up/Down zoom, so you can't long press them to pan - // Key.DirectionLeft -> panX += with(density) { 15.dp.toPx() } - // Key.DirectionRight -> panX -= with(density) { 15.dp.toPx() } - } - return@onKeyEvent true - } - } - if (it.type != KeyEventType.KeyUp) { - result = false - } else if (!isOverlayShowing && isZoomed && isDirectionalDpad(it)) { - // Image is zoomed in - when (it.key) { - Key.DirectionLeft -> pan(30, 0) - Key.DirectionRight -> pan(-30, 0) - Key.DirectionUp -> pan(0, 30) - Key.DirectionDown -> pan(0, -30) - } - result = true - } else if (!isOverlayShowing && isZoomed && it.key == Key.Back) { - reset(false) - result = true - } else if (!isOverlayShowing && (it.key == Key.DirectionLeft || it.key == Key.DirectionRight)) { - when (it.key) { - Key.DirectionLeft, Key.DirectionUpLeft, Key.DirectionDownLeft -> { - if (!viewModel.previousImage()) { - Toast - .makeText( - context, - R.string.slideshow_at_beginning, - Toast.LENGTH_SHORT, - ).show() - } - } - - Key.DirectionRight, Key.DirectionUpRight, Key.DirectionDownRight -> { - if (!viewModel.nextImage()) { - Toast - .makeText( - context, - R.string.no_more_images, - Toast.LENGTH_SHORT, - ).show() - } - } - } - } else if (isOverlayShowing && it.key == Key.Back) { - showOverlay = false - viewModel.unpauseSlideshow() - result = true - } else if (!isOverlayShowing && (isDpad(it) || isEnterKey(it))) { - showOverlay = true - viewModel.pauseSlideshow() - result = true - } - if (result) { - // Handled the key, so reset the slideshow timer - viewModel.pulseSlideshow() - } - result - }, - ) { - when (loadingState) { - ImageLoadingState.Error -> { - ErrorMessage("Error loading image", null, modifier) - } - - ImageLoadingState.Loading -> { - LoadingPage(modifier) - } - - is ImageLoadingState.Success -> { - imageState?.let { imageState -> - if (imageState.image.data.mediaType == MediaType.VIDEO) { - LaunchedEffect(imageState.id) { - val mediaItem = - MediaItem - .Builder() - .setUri(imageState.url) - .build() - player.setMediaItem(mediaItem) - player.repeatMode = - if (slideshowState.enabled) { - Player.REPEAT_MODE_OFF - } else { - Player.REPEAT_MODE_ONE - } - player.prepare() - player.play() - viewModel.pulseSlideshow(Long.MAX_VALUE) - } - LifecycleStartEffect(Unit) { - onStopOrDispose { - player.stop() - } - } - val contentScale = ContentScale.Fit - val scaledModifier = - contentModifier.resizeWithContentScale( - contentScale, - presentationState.videoSizeDp, - ) - PlayerSurface( - player = player, - surfaceType = SURFACE_TYPE_SURFACE_VIEW, - modifier = - scaledModifier - .fillMaxSize() - .graphicsLayer { - scaleX = zoomAnimation - scaleY = zoomAnimation - translationX = panXAnimation - translationY = panYAnimation - }.rotate(rotateAnimation), - ) - if (presentationState.coverSurface) { - Box( - Modifier - .matchParentSize() - .background(Color.Black), - ) - } - } else { - val colorFilter = - remember(imageState.id, imageFilter) { - if (imageFilter.hasImageFilter()) { - ColorMatrixColorFilter(imageFilter.colorMatrix) - } else { - null - } - } - // If the image loading is large, show the thumbnail while waiting - // TODO - val showLoadingThumbnail = true - SubcomposeAsyncImage( - modifier = - contentModifier - .fillMaxSize() - .graphicsLayer { - scaleX = zoomAnimation - scaleY = zoomAnimation - translationX = panXAnimation - translationY = panYAnimation - - val xTransform = - (screenWidth - panXAnimation) / (screenWidth * 2) - val yTransform = - (screenHeight - panYAnimation) / (screenHeight * 2) - if (DEBUG) { - Timber.d( - "graphicsLayer: xTransform=$xTransform, yTransform=$yTransform", - ) - } - - transformOrigin = - TransformOrigin(xTransform, yTransform) - }.rotate(rotateAnimation), - model = - ImageRequest - .Builder(LocalContext.current) - .data(imageState.url) - .size(Size.ORIGINAL) - .crossfade(!showLoadingThumbnail) - .build(), - contentDescription = null, - contentScale = ContentScale.Fit, - colorFilter = colorFilter, - error = { - Text( - modifier = - Modifier - .align(Alignment.Center), - text = "Error loading image", - color = MaterialTheme.colorScheme.onBackground, - ) - }, - loading = { - ImageLoadingPlaceholder( - thumbnailUrl = imageState.thumbnailUrl, - showThumbnail = showLoadingThumbnail, - colorFilter = colorFilter, - modifier = Modifier.fillMaxSize(), - ) - }, - // Ensure that if an image takes a long time to load, it won't be skipped - onLoading = { - viewModel.pulseSlideshow(Long.MAX_VALUE) - }, - onSuccess = { - viewModel.pulseSlideshow() - }, - onError = { - Timber.e( - it.result.throwable, - "Error loading image ${imageState.id}", - ) - Toast - .makeText( - context, - "Error loading image: ${it.result.throwable.localizedMessage}", - Toast.LENGTH_LONG, - ).show() - viewModel.pulseSlideshow() - }, - ) + // These work, but feel awkward because Up/Down zoom, so you can't long press them to pan + // Key.DirectionLeft -> panX += with(density) { 15.dp.toPx() } + // Key.DirectionRight -> panX -= with(density) { 15.dp.toPx() } } + return@onKeyEvent true } } - } - AnimatedVisibility( - showOverlay, - enter = slideInVertically { it }, - exit = slideOutVertically { it }, - modifier = Modifier.align(Alignment.BottomStart), - ) { - imageState?.let { imageState -> - ImageOverlay( + if (it.type != KeyEventType.KeyUp) { + result = false + } else if (!isOverlayShowing && isZoomed && isDirectionalDpad(it)) { + // Image is zoomed in + when (it.key) { + Key.DirectionLeft -> pan(30, 0) + Key.DirectionRight -> pan(-30, 0) + Key.DirectionUp -> pan(0, 30) + Key.DirectionDown -> pan(0, -30) + } + result = true + } else if (!isOverlayShowing && isZoomed && it.key == Key.Back) { + reset(false) + result = true + } else if (!isOverlayShowing && (it.key == Key.DirectionLeft || it.key == Key.DirectionRight)) { + when (it.key) { + Key.DirectionLeft, Key.DirectionUpLeft, Key.DirectionDownLeft -> { + if (!viewModel.previousImage()) { + Toast + .makeText( + context, + R.string.slideshow_at_beginning, + Toast.LENGTH_SHORT, + ).show() + } + } + + Key.DirectionRight, Key.DirectionUpRight, Key.DirectionDownRight -> { + if (!viewModel.nextImage()) { + Toast + .makeText( + context, + R.string.no_more_images, + Toast.LENGTH_SHORT, + ).show() + } + } + } + } else if (isOverlayShowing && it.key == Key.Back) { + showOverlay = false + viewModel.unpauseSlideshow() + result = true + } else if (!isOverlayShowing && (isDpad(it) || isEnterKey(it))) { + showOverlay = true + viewModel.pauseSlideshow() + result = true + } + if (result) { + // Handled the key, so reset the slideshow timer + viewModel.pulseSlideshow() + } + result + }, + ) { + when (loadingState) { + ImageLoadingState.Error -> { + ErrorMessage("Error loading image", null, modifier) + } + + ImageLoadingState.Loading -> { + LoadingPage(modifier) + } + + is ImageLoadingState.Success -> { + imageState?.let { imageState -> + if (imageState.image.data.mediaType == MediaType.VIDEO) { + LaunchedEffect(imageState.id) { + val mediaItem = + MediaItem + .Builder() + .setUri(imageState.url) + .build() + player.setMediaItem(mediaItem) + player.repeatMode = + if (slideshowState.enabled) { + Player.REPEAT_MODE_OFF + } else { + Player.REPEAT_MODE_ONE + } + player.prepare() + player.play() + viewModel.pulseSlideshow(Long.MAX_VALUE) + } + LifecycleStartEffect(Unit) { + onStopOrDispose { + player.stop() + } + } + val contentScale = ContentScale.Fit + val scaledModifier = + contentModifier.resizeWithContentScale( + contentScale, + presentationState.videoSizeDp, + ) + PlayerSurface( + player = player, + surfaceType = SURFACE_TYPE_SURFACE_VIEW, + modifier = + scaledModifier + .fillMaxSize() + .graphicsLayer { + scaleX = zoomAnimation + scaleY = zoomAnimation + translationX = panXAnimation + translationY = panYAnimation + }.rotate(rotateAnimation), + ) + if (presentationState.coverSurface) { + Box( + Modifier + .matchParentSize() + .background(Color.Black), + ) + } + } else { + val colorFilter = + remember(imageState.id, imageFilter) { + if (imageFilter.hasImageFilter()) { + ColorMatrixColorFilter(imageFilter.colorMatrix) + } else { + null + } + } + // If the image loading is large, show the thumbnail while waiting + // TODO + val showLoadingThumbnail = true + SubcomposeAsyncImage( modifier = contentModifier - .fillMaxWidth() - .background(AppColors.TransparentBlack50), - onDismiss = { showOverlay = false }, - player = player, - slideshowControls = slideshowControls, - slideshowEnabled = slideshowState.enabled, - image = imageState, - position = position, - count = pager?.size ?: -1, - onClickItem = {}, - onLongClickItem = {}, - onZoom = ::zoom, - onRotate = { rotation += it }, - onReset = { reset(true) }, - onShowFilterDialogClick = { - showFilterDialog = true - showOverlay = false - viewModel.pauseSlideshow() + .fillMaxSize() + .graphicsLayer { + scaleX = zoomAnimation + scaleY = zoomAnimation + translationX = panXAnimation + translationY = panYAnimation + + val xTransform = + (screenWidth - panXAnimation) / (screenWidth * 2) + val yTransform = + (screenHeight - panYAnimation) / (screenHeight * 2) + if (DEBUG) { + Timber.d( + "graphicsLayer: xTransform=$xTransform, yTransform=$yTransform", + ) + } + + transformOrigin = TransformOrigin(xTransform, yTransform) + }.rotate(rotateAnimation), + model = + ImageRequest + .Builder(LocalContext.current) + .data(imageState.url) + .size(Size.ORIGINAL) + .crossfade(!showLoadingThumbnail) + .build(), + contentDescription = null, + contentScale = ContentScale.Fit, + colorFilter = colorFilter, + error = { + Text( + modifier = + Modifier + .align(Alignment.Center), + text = "Error loading image", + color = MaterialTheme.colorScheme.onBackground, + ) + }, + loading = { + ImageLoadingPlaceholder( + thumbnailUrl = imageState.thumbnailUrl, + showThumbnail = showLoadingThumbnail, + colorFilter = colorFilter, + modifier = Modifier.fillMaxSize(), + ) + }, + // Ensure that if an image takes a long time to load, it won't be skipped + onLoading = { + viewModel.pulseSlideshow(Long.MAX_VALUE) + }, + onSuccess = { + viewModel.pulseSlideshow() + }, + onError = { + Timber.e( + it.result.throwable, + "Error loading image ${imageState.id}", + ) + Toast + .makeText( + context, + "Error loading image: ${it.result.throwable.localizedMessage}", + Toast.LENGTH_LONG, + ).show() + viewModel.pulseSlideshow() }, ) } } - AnimatedVisibility(showFilterDialog) { - ImageFilterDialog( - filter = imageFilter, - showVideoOptions = false, - showSaveGalleryButton = true, - onChange = viewModel::updateImageFilter, - onClickSave = viewModel::saveImageFilter, - onClickSaveGallery = viewModel::saveGalleryFilter, - onDismissRequest = { - showFilterDialog = false - viewModel.unpauseSlideshow() - viewModel.pulseSlideshow() - }, - ) - } } } + AnimatedVisibility( + showOverlay, + enter = slideInVertically { it }, + exit = slideOutVertically { it }, + modifier = Modifier.align(Alignment.BottomStart), + ) { + imageState?.let { imageState -> + ImageOverlay( + modifier = + contentModifier + .fillMaxWidth() + .background(AppColors.TransparentBlack50), + onDismiss = { showOverlay = false }, + player = player, + slideshowControls = slideshowControls, + slideshowEnabled = slideshowState.enabled, + image = imageState, + position = position, + count = pager?.size ?: -1, + onClickItem = {}, + onLongClickItem = {}, + onZoom = ::zoom, + onRotate = { rotation += it }, + onReset = { reset(true) }, + onShowFilterDialogClick = { + showFilterDialog = true + showOverlay = false + viewModel.pauseSlideshow() + }, + ) + } + } + AnimatedVisibility(showFilterDialog) { + ImageFilterDialog( + filter = imageFilter, + showVideoOptions = false, + showSaveGalleryButton = true, + onChange = viewModel::updateImageFilter, + onClickSave = viewModel::saveImageFilter, + onClickSaveGallery = viewModel::saveGalleryFilter, + onDismissRequest = { + showFilterDialog = false + viewModel.unpauseSlideshow() + viewModel.pulseSlideshow() + }, + ) + } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowViewModel.kt index b86814ac..992659ca 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowViewModel.kt @@ -16,7 +16,6 @@ import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.PlaybackEffect import com.github.damontecres.wholphin.data.model.VideoFilter import com.github.damontecres.wholphin.preferences.AppPreference -import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.PlayerFactory import com.github.damontecres.wholphin.services.ScreensaverService @@ -31,7 +30,6 @@ import com.github.damontecres.wholphin.ui.util.ThrottledLiveData import com.github.damontecres.wholphin.util.ApiRequestPager import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler -import com.github.damontecres.wholphin.util.LoadingState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -79,8 +77,9 @@ class SlideshowViewModel fun create(slideshow: Destination.Slideshow): SlideshowViewModel } - lateinit var player: Player - private set + val player by lazy { + playerFactory.createVideoPlayer() + } private var saveFilters = true @@ -105,8 +104,6 @@ class SlideshowViewModel private val _image = MutableLiveData() val image: LiveData = _image - val loading = MutableStateFlow(LoadingState.Pending) - val loadingState = MutableLiveData(ImageLoadingState.Loading) private val _imageFilter = MutableLiveData(VideoFilter()) val imageFilter = ThrottledLiveData(_imageFilter, 500L) @@ -116,67 +113,58 @@ class SlideshowViewModel init { addCloseable { screensaverService.keepScreenOn(false) - if (this@SlideshowViewModel::player.isInitialized) { - player.removeListener(this@SlideshowViewModel) - player.release() - } + player.removeListener(this@SlideshowViewModel) + player.release() } + player.addListener(this@SlideshowViewModel) viewModelScope.launchIO { - try { - val appPreferences = userPreferencesService.getCurrent().appPreferences - val playerCreation = - playerFactory.createVideoPlayer( - backend = PlayerBackend.EXO_PLAYER, - appPreferences.playbackPreferences, - ) - player = playerCreation.player - player.addListener(this@SlideshowViewModel) - - val photoPrefs = appPreferences.photoPreferences - slideshowDelay = - photoPrefs.slideshowDuration.takeIf { it >= AppPreference.SlideshowDuration.min } - ?: AppPreference.SlideshowDuration.defaultValue - val includeItemTypes = - if (photoPrefs.slideshowPlayVideos) { - listOf(BaseItemKind.PHOTO, BaseItemKind.VIDEO) - } else { - listOf(BaseItemKind.PHOTO) - } - val request = - slideshowSettings.filter.filter.applyTo( - GetItemsRequest( - parentId = slideshowSettings.parentId, - includeItemTypes = includeItemTypes, - fields = PhotoItemFields, - recursive = true, - sortBy = listOf(slideshowSettings.sortAndDirection.sort), - sortOrder = listOf(slideshowSettings.sortAndDirection.direction), - ), - ) - serverRepository.currentUser.value?.let { user -> - val filter = - playbackEffectDao - .getPlaybackEffect( - user.rowId, - slideshowSettings.parentId, - BaseItemKind.PHOTO_ALBUM, - )?.videoFilter - if (filter != null) { - Timber.v("Got filter for album %s", slideshowSettings.parentId) - albumImageFilter = filter - } + val photoPrefs = userPreferencesService.getCurrent().appPreferences.photoPreferences + slideshowDelay = + photoPrefs.slideshowDuration.takeIf { it >= AppPreference.SlideshowDuration.min } + ?: AppPreference.SlideshowDuration.defaultValue +// val album = +// api.userLibraryApi +// .getItem( +// itemId = slideshowSettings.parentId, +// ).content +// .let { BaseItem(it, false) } +// this@SlideshowViewModel.album.setValueOnMain(album) + val includeItemTypes = + if (photoPrefs.slideshowPlayVideos) { + listOf(BaseItemKind.PHOTO, BaseItemKind.VIDEO) + } else { + listOf(BaseItemKind.PHOTO) + } + val request = + slideshowSettings.filter.filter.applyTo( + GetItemsRequest( + parentId = slideshowSettings.parentId, + includeItemTypes = includeItemTypes, + fields = PhotoItemFields, + recursive = true, + sortBy = listOf(slideshowSettings.sortAndDirection.sort), + sortOrder = listOf(slideshowSettings.sortAndDirection.direction), + ), + ) + serverRepository.currentUser.value?.let { user -> + val filter = + playbackEffectDao + .getPlaybackEffect( + user.rowId, + slideshowSettings.parentId, + BaseItemKind.PHOTO_ALBUM, + )?.videoFilter + if (filter != null) { + Timber.v("Got filter for album %s", slideshowSettings.parentId) + albumImageFilter = filter } - val pager = - ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) - .init(slideshowSettings.index) - this@SlideshowViewModel._pager.setValueOnMain(pager) - loading.update { LoadingState.Success } - updatePosition(slideshowSettings.index)?.join() - if (slideshowSettings.startSlideshow) onMain { startSlideshow() } - } catch (ex: Exception) { - Timber.e(ex, "Error") - loading.update { LoadingState.Error(ex) } } + val pager = + ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) + .init(slideshowSettings.index) + this@SlideshowViewModel._pager.setValueOnMain(pager) + updatePosition(slideshowSettings.index)?.join() + if (slideshowSettings.startSlideshow) onMain { startSlideshow() } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5136c83a..c7a2fb92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -312,7 +312,7 @@ Check for updates Applies to TV Series only - Use libass for ASS subtitles + Direct play ASS subtitles Direct play PGS subtitles Always downmix to stereo Use FFmpeg decoder module diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a4de2268..b60ca106 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,6 @@ preferenceKtx = "1.2.1" tvprovider = "1.1.0" workRuntimeKtx = "2.11.1" paletteKtx = "1.0.0" -assMedia = "0.4.0" kotlinxCoroutinesTest = "1.10.2" coreTesting = "2.2.0" openapi-generator = "7.20.0" @@ -135,8 +134,6 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t androidx-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "coreTesting" } androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" } -ass-media = { group = "io.github.peerless2012", name = "ass-media", version.ref = "assMedia" } - [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } From 781c020b67cf33c9bafdb974bcfd13057e1c284c Mon Sep 17 00:00:00 2001 From: danpergal84 Date: Tue, 24 Feb 2026 22:20:33 +0000 Subject: [PATCH 02/45] Translated using Weblate (Spanish) Currently translated at 100.0% (472 of 472 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b4ef66da..3fe2aa97 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -35,7 +35,7 @@ Nombre Sin resultados Ninguno - Outro + Créditos finales Personas Reproducir Reproducción @@ -264,8 +264,8 @@ Retroceder Comportamiento al saltar anuncios Avanzar - Comportamiento al saltar la intro - Comportamiento al saltar el outro + Comportamiento al saltar la cabecera + Comportamiento al saltar créditos finales Comportamiento al saltar avances Comportamiento al saltar resumen Actualización disponible @@ -291,8 +291,8 @@ Saltar anuncios Saltar avance Saltar resumen - Saltar outro - Saltar intro + Saltar créditos finales + Saltar cabecera Reproducido FIltrar Año @@ -485,8 +485,8 @@ Rellenar Ajustar al ancho Ajustar al alto - Miniaturas de las series - Miniaturas de los episodios + Miniatura de la serie + Miniatura del episodio Mínimo Bajo Medio From a479afd10a564debbe358fa46f25755864f2f516 Mon Sep 17 00:00:00 2001 From: SimonHung Date: Wed, 25 Feb 2026 07:34:03 +0000 Subject: [PATCH 03/45] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (472 of 472 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 46 ++++++++++++++++------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6670d10e..b325893a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -59,7 +59,7 @@ 指南 更新 版本 - 畫面比例 + 畫面縮放比例 影片 影片 觀看直播 @@ -104,7 +104,7 @@ 直接播放 ASS 字幕 直接播放 PGS 字幕 使用 FFmpeg 解碼模組 - 預設畫面比例 + 預設縮放比例 安裝更新 安裝版本 首頁每列項目上限 @@ -362,7 +362,7 @@ 移除 Seerr 伺服器 已新增 Seerr 伺服器 密碼 - 使用者名稱 + 帳號 URL 當前趨勢 即將上映的電影 @@ -427,18 +427,18 @@ 推薦的 %1$s 放大所有海報尺寸 縮小所有海報尺寸 - 使用橫向縮圖 + 使用橫式縮圖 新增一列 套用到所有列 首頁各列 為 %1$s 新增列項目 快速設定所有項目為內建預設樣式 - 選擇首頁要顯示的列項目及圖片樣式 + 選擇首頁要顯示的項目及圖片樣式 高度 自訂首頁 從伺服器載入設定 保存設定到伺服器 - 從網頁版載入 + 從網頁版載入設定 使用劇集圖片 覆蓋伺服器上的設定? 覆蓋本地設定? @@ -450,11 +450,11 @@ 拉伸填滿 填滿寬度 填滿高度 - 最低 - - 中等 - - 最大 + 最低音量 + 低音量 + 中等音量 + 高音量 + 最大音量 紫色 橘色 深藍色 @@ -472,10 +472,32 @@ 深灰色 黃色 青色 - 洋紅色 + 桃紅色 外框 陰影 貼合文字 長方底框 優先使用 MPV + 僅 HDR 使用 ExoPlayer 播放 + 我的最愛的 %s + Wholphin 預設樣式 + Wholphin 緊湊樣式 + 劇集縮圖樣式 + 單集縮圖樣式 + 海報 (2:3) + 16:9 + 4:3 + 正方形 (1:1) + 主封面 + 橫式縮圖 + 動態色彩背景 + 僅背景圖 + + API 金鑰 + 登入 Seerr 伺服器 + Jellyfin 帳號 + Seerr 帳號 + + 未找到遠端字幕 + 至今 From cf369b26de34155f354f45e4d982e861d1c04bbb Mon Sep 17 00:00:00 2001 From: arcker95 Date: Wed, 25 Feb 2026 14:39:29 +0000 Subject: [PATCH 04/45] Translated using Weblate (Italian) Currently translated at 100.0% (472 of 472 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a3764b96..96b191c7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -480,4 +480,58 @@ Usa immagine delle serie Scegli righe e immagini nella home page Preferito %s + Adatta + Ritaglia + Riempi + Riempi larghezza + Riempi altezza + Wholphin predefinito + Wholphin Compatto + Immagini miniatura serie + Immagini miniatura episodio + Minimo + Basso + Medio + Alto + Massimo + Viola + Arancione + Blu intenso + Nero + Ignora + Salta automaticamente + Chiedi se saltare + Usa FFmpeg solo se non esiste un decodificatore integrato + Preferisci FFmpeg ai decoder integrati + Non usare mai i decoder FFmpeg + Alla fine della riproduzione + Durante i titoli di coda + Bianco + Grigio chiaro + Grigio scuro + Giallo + Ciano + Magenta + Contorno + Ombra + Avvolgi + Con riquadro + Preferisci MVP + Usa ExoPlayer per la riproduzione HDR + Copertina (2:3) + 16:9 + 4:3 + Quadrato (1:1) + Principale + Miniatura + Immagine con colore dinamico + Solo immagine + + Chiave API + Accedi al server Seerr + Utente Jellyfin + Utente locale + + Nessun sottotitolo remoto trovato + In corso From 0f8b5db397c0a108e1813c7a92c6e3fb033fcac1 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Wed, 25 Feb 2026 13:44:13 +0000 Subject: [PATCH 05/45] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (472 of 472 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 28875ea9..a49a19cf 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -446,4 +446,58 @@ 可快速设置所有行样式的内置预设 选择首页上的行和图片 收藏的 %s + 适应 + 裁剪 + 填充 + 填充宽度 + 填充高度 + Wholphin 默认 + Wholphin 紧凑 + 剧集缩略图 + 分集缩略图 + 紫色 + 橙色 + 深蓝色 + 黑色 + 忽略 + 自动跳过 + 询问后跳过 + 仅在无内置解码器时使用 FFmpeg + 优先使用 FFmpeg 解码器而非内置解码器 + 从不使用 FFmpeg 解码器 + 播放结束时 + 播到片尾时 + 白色 + 浅灰色 + 深灰色 + 黄色 + 青色 + 紫红色 + 轮廓 + 阴影 + 优先使用 mpv + HDR 播放使用 ExoPlayer + 海报 (2:3) + 16:9 + 4:3 + 正方形 (1:1) + 主图 + 缩略图 + 动态配色图片 + 仅图片 + + API 密钥 + 登录 Seerr 服务器 + Jellyfin 用户 + 本地用户 + + 未找到远程字幕 + 最低音量 + 低音量 + 中等音量 + 高音量 + 最大音量 + 环绕式 + 框式 + 当前 From 0c94a0dbd62a76e10f3bd11b7068d656adeff92c Mon Sep 17 00:00:00 2001 From: idezentas Date: Tue, 24 Feb 2026 20:36:18 +0000 Subject: [PATCH 06/45] Translated using Weblate (Turkish) Currently translated at 100.0% (472 of 472 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/ --- app/src/main/res/values-tr/strings.xml | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index aec35e54..7de70d74 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -472,4 +472,58 @@ Tüm satırları hızlıca stilize etmek için hazır ayarlar Favori %s Ana sayfadaki satırları ve görselleri seçin + Sığdır + Kırp + Doldur + Genişliği Doldur + Yüksekliği Doldur + Wholphin Varsayılan + Wholphin Kompakt + Dizi Küçük Resimleri + Bölüm Küçük Resimleri + En Düşük + Düşük + Orta + Yüksek + Tam + Mor + Turuncu + Koyu Mavi + Siyah + Yoksay + Otomatik Atla + Atlamak için sor + Dahili çözücü yoksa FFmpeg kullan + Dahili çözücüler yerine FFmpeg\'i tercih et + FFmpeg çözücülerini asla kullanma + Oynatma sonunda + Kapanış bölümünde / Jenerik sırasında + Beyaz + Açık Gri + Koyu Gri + Sarı + Camgöbeği + Macenta + Kenarlık + Gölge + Kapla + Kutu içinde + MPV\'yi tercih et + HDR oynatma için ExoPlayer kullan + Poster (2:3) + 16:9 + 4:3 + Kare (1:1) + Birincil + Küçük Resim + Dinamik renkli görsel + Sadece görsel + + API Anahtarı + Seerr sunucusuna giriş yap + Jellyfin kullanıcısı + Yerel kullanıcı + + Uzak altyazı bulunamadı + Mevcut From c5d6eaa2719f4e6de5baa43ab0be15b923cbd767 Mon Sep 17 00:00:00 2001 From: arcker95 Date: Thu, 26 Feb 2026 01:36:32 +0000 Subject: [PATCH 07/45] Translated using Weblate (Italian) Currently translated at 97.1% (473 of 487 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 96b191c7..1fb27ae7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -534,4 +534,9 @@ Nessun sottotitolo remoto trovato In corso + + %s minuto + %s minuti + %s minuti + From 691ae4a1a4db8831cf8d11a3363cafbfcdb330c1 Mon Sep 17 00:00:00 2001 From: danpergal84 Date: Thu, 26 Feb 2026 16:35:52 +0000 Subject: [PATCH 08/45] Translated using Weblate (Spanish) Currently translated at 100.0% (487 of 487 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3fe2aa97..c4b70a44 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -534,4 +534,23 @@ No se encontraron subtítulos externos Actualidad + + %s minuto + %s minutos + %s minutos + + Comenzar después de + Animación + Clasificación por edad máxima + Para todas las edades + Menores de %s + Salvapantallas + Configuración de salvapantallas + Iniciar salvapantallas + Duración + Fotos + Incluir tipos + Qué tipos de elementos mostrar + Usar salvapantallas de la aplicación + Sin límite From 898191e44af874a08e35891a7374bd867347443b Mon Sep 17 00:00:00 2001 From: American_Jesus Date: Thu, 26 Feb 2026 13:45:09 +0000 Subject: [PATCH 09/45] Translated using Weblate (Portuguese) Currently translated at 93.6% (456 of 487 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt/ --- app/src/main/res/values-pt/strings.xml | 47 ++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 1fb5fbb5..45d0fcd2 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -463,8 +463,8 @@ Aplicar a todas as linhas Personalizar página inicial Linhas da página inicial - Carregar do servidor - Gravar para o servidor + Carregar do perfil de utilizador do servidor + Guardar no perfil de utilizador do servidor Carregar a partir do cliente web Usar imagem da série Adicionar linha para %1$s @@ -479,4 +479,47 @@ Predefinições embarcadas para definir todas as linhas rapidamente Escolha as linhas e imagens na página inicial Diminuir o tamanho de todos os cartões + Favoritos %s + + %s minuto + %s minutos + %s minutos + + Começar depois + Animado + Idade máxima + Sem máximo + Para todas as idades + Até a idade %s + Proteção de ecrã + Definições de protecção de ecrã + Iniciar protecção de ecrã + Duração + Fotos + Incluir tipos + Que tipos de itens devem mostrar imagens + Ajustar + Recortar + Preencher + Preencher largura + Preencher altura + Padrão do Wholphin + Wholphin Compacto + Imagens em miniatura da série + Imagens em miniatura do episódio + Mínimo + Baixo + Médio + Alto + Máximo + Roxo + Laranja + Azul intenso + Preto + Ignorar + Saltar automaticamente + Pedir para saltar + Usa FFmpeg apenas se não houver um descodificador integrado + Prefere o FFmpeg aos descodificadores integrados + Nunca usar os descodificadores FFmpeg From a1d651ae21a7101e92ff142682c31ecf96f1d29b Mon Sep 17 00:00:00 2001 From: SimonHung Date: Thu, 26 Feb 2026 04:01:50 +0000 Subject: [PATCH 10/45] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 97.1% (473 of 487 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b325893a..0dac3571 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -500,4 +500,7 @@ 未找到遠端字幕 至今 + + %s 分鐘 + From 034f91ba3a7d480223c90f43549ed688a3e558aa Mon Sep 17 00:00:00 2001 From: arcker95 Date: Thu, 26 Feb 2026 01:43:55 +0000 Subject: [PATCH 11/45] Translated using Weblate (Italian) Currently translated at 100.0% (487 of 487 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 1fb27ae7..91e299b6 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -539,4 +539,18 @@ %s minuti %s minuti + Inizia dopo + Anima + Età massima + Per tutte le età + Fino a %s anni + Salvaschermo + Impostazioni salvaschermo + Avvia salvaschermo + Durata + Foto + Includi tipi + Quali tipi di elementi mostrare nelle immagini + Nessun massimo + Usa salvaschermo dell\'app From c5316343b9d3db893f0ad9b3ec33054b5a6e1f82 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 26 Feb 2026 13:41:46 +0000 Subject: [PATCH 12/45] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (487 of 487 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a49a19cf..2ec747ca 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -150,7 +150,7 @@ 字体 背景 成功 - 年龄分级 + 内容分级 播放时长 附加内容 @@ -500,4 +500,21 @@ 环绕式 框式 当前 + + %s 分钟 + + 动画 + 启动时间 + 最高年龄分级 + 无上限 + 所有年龄 + 最高至 %s 岁 + 屏幕保护程序 + 屏幕保护程序设置 + 启动屏幕保护程序 + 时长 + 照片 + 包含类型 + 要显示图片的项目类型 + 使用应用内屏幕保护程序 From 9aaee3033ee57b9a5565d2add766a9ff86a5f624 Mon Sep 17 00:00:00 2001 From: idezentas Date: Thu, 26 Feb 2026 20:25:19 +0000 Subject: [PATCH 13/45] Translated using Weblate (Turkish) Currently translated at 100.0% (487 of 487 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/ --- app/src/main/res/values-tr/strings.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 7de70d74..9a1d41bb 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -526,4 +526,22 @@ Uzak altyazı bulunamadı Mevcut + + %s dakika + %s dakika + + Başlama süresi + Oynatma Süresi + Maksimum yaş sınırı + Sınır yok + Genel İzleyici + %s yaşa kadar + Ekran koruyucu + Ekran koruyucu ayarları + Ekran koruyucuyu başlat + Süre + Fotoğraflar + İçerik türleri + Görsel gösterilecek öğe türleri + Uygulama içi ekran koruyucuyu kullan From 7432df3eb18d074dd9bd3d4f34f08b1260c828ed Mon Sep 17 00:00:00 2001 From: SimonHung Date: Fri, 27 Feb 2026 05:58:24 +0000 Subject: [PATCH 14/45] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (499 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 0dac3571..8a1478a9 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -503,4 +503,30 @@ %s 分鐘 + 啟動時間 + 動態效果 + 年齡分級上限 + 不限制 + 適合所有年齡 + 最高至 %s 歲 + 螢幕保護程式 + 螢幕保護程式設定 + 立即開始 + 切換間隔 + 照片 + 包含類型 + 要顯示圖片的類型 + 使用內建螢幕保護程式 + 客串演員 + 演員 + 作曲 + 編劇 + 製作人 + 指揮 + 作詞 + 編曲 + 錄音師 + 混音師 + 創作者 + 演出者 From fd53206aeeed8db517995bd778248c02fa44d765 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Fri, 27 Feb 2026 04:35:44 +0000 Subject: [PATCH 15/45] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (499 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2ec747ca..ad8008a2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -517,4 +517,16 @@ 包含类型 要显示图片的项目类型 使用应用内屏幕保护程序 + 演员 + 作曲 + 编剧 + 客串 + 制片 + 指挥 + 作词 + 编曲 + 录音 + 混音 + 主创 + 艺人 From b2aea426ef964eb589c279745bddfccf73e94579 Mon Sep 17 00:00:00 2001 From: danpergal84 Date: Fri, 27 Feb 2026 14:03:03 +0000 Subject: [PATCH 16/45] Translated using Weblate (Spanish) Currently translated at 100.0% (499 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c4b70a44..a6340bc5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -553,4 +553,16 @@ Qué tipos de elementos mostrar Usar salvapantallas de la aplicación Sin límite + Intérprete + Compositor + Guionista + Estrella invitada + Productor + Director musical + Letrista + Arreglista + Ingeniero de sonido + Ingeniero de mezcla + Creador + Artista From ea4e4ad1409c52587df64b6ded4d1a10f901a7af Mon Sep 17 00:00:00 2001 From: arcker95 Date: Fri, 27 Feb 2026 14:39:11 +0000 Subject: [PATCH 17/45] Translated using Weblate (Italian) Currently translated at 100.0% (499 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 91e299b6..2f22c8d4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -553,4 +553,16 @@ Quali tipi di elementi mostrare nelle immagini Nessun massimo Usa salvaschermo dell\'app + Compositore + Sceneggiatore + Attore + Produttore + Ospite speciale + Direttore d’orchestra + Paroliere + Arrangiatore + Ingegnere del suono + Tecnico di mixaggio + Artista + Creatore From 321ef8965a39236d1debe1c76ac536cb34ef7dec Mon Sep 17 00:00:00 2001 From: idezentas Date: Fri, 27 Feb 2026 09:18:21 +0000 Subject: [PATCH 18/45] Translated using Weblate (Turkish) Currently translated at 100.0% (499 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/ --- app/src/main/res/values-tr/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9a1d41bb..636a6349 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -544,4 +544,16 @@ İçerik türleri Görsel gösterilecek öğe türleri Uygulama içi ekran koruyucuyu kullan + Aktör + Besteci + Yazar + Konuk sanatçı + Yapımcı + Orkestra şefi + Söz yazarı + Aranjör + Mühendis + Karıştırıcı + Yaratıcı + Sanatçı From 9daa6c9337c55c9610e87e06f950c55c0e14704b Mon Sep 17 00:00:00 2001 From: EmadAljohani Date: Sat, 28 Feb 2026 04:27:59 +0000 Subject: [PATCH 19/45] Added translation using Weblate (Arabic) --- app/src/main/res/values-ar/strings.xml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/src/main/res/values-ar/strings.xml diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 00000000..55344e51 --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 46c79938363d35bec14f486dc30d0f7a703cc530 Mon Sep 17 00:00:00 2001 From: American_Jesus Date: Sat, 28 Feb 2026 14:26:37 +0000 Subject: [PATCH 20/45] Translated using Weblate (Portuguese) Currently translated at 100.0% (499 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt/ --- app/src/main/res/values-pt/strings.xml | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 45d0fcd2..62cafc3f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -522,4 +522,47 @@ Usa FFmpeg apenas se não houver um descodificador integrado Prefere o FFmpeg aos descodificadores integrados Nunca usar os descodificadores FFmpeg + No final da reprodução + Durante os créditos finais + Branco + Cinzento claro + Cinzento escuro + Amarelo + Ciano + Magenta + Contorno + Sombra + Envolver + Com caixa + Preferir MPV + Usar ExoPlayer para reprodução HDR + Poster (2:3) + 16:9 + 4:3 + Quadrado (1:1) + Primária + Miniatura + Imagem com cor dinâmica + Apenas imagem + + Chave API + Iniciar sessão no servidor Seerr + Utilizador Jellyfin + Utilizador local + + Não foram encontradas legendas remotas + Em curso + Usar protetor de ecrã da aplicação + Actor + Compositor + Argumentista + Convidado especial + Produtor + Director musicar + Letrista + Arranjador + Engenheiro de som + Técnico de mixagem + Criador + Artista From 49667bc053bc6f4bba2bf96e427f105103f74bc9 Mon Sep 17 00:00:00 2001 From: zyplex Date: Sun, 1 Mar 2026 16:30:22 +0000 Subject: [PATCH 21/45] Translated using Weblate (German) Currently translated at 79.1% (395 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/ --- app/src/main/res/values-de/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 013f5ccd..8076b47f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -432,4 +432,18 @@ Videos während der Diashow abspielen Maximale Tage in \"Als nächstes\" Quick Connect + + %s Minute + %s Minuten + + Interlaced + NAL + Gewähre anderen Geräten Zugriff auf deinen Account + Eingabe Quick Connect Code + Keine Beschränkung + Reihe hinzufügen + Genres in %1$s + kürzlich veröffentlicht in %1$s + Höhe + Auf alle Reihen anwenden From 3284a400842783dc52abe729b324a60f523b33e6 Mon Sep 17 00:00:00 2001 From: American_Jesus Date: Mon, 2 Mar 2026 02:27:25 +0000 Subject: [PATCH 22/45] Translated using Weblate (Portuguese) Currently translated at 100.0% (499 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt/ --- app/src/main/res/values-pt/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 62cafc3f..6d8ac4e0 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -121,7 +121,7 @@ Mais Votados Não Assistidos Trailer - Trailers + Trailer Trailers Trailers @@ -553,7 +553,7 @@ Não foram encontradas legendas remotas Em curso Usar protetor de ecrã da aplicação - Actor + Ator Compositor Argumentista Convidado especial From 01ac0dec4cb2ea0a2800ec8810b0098624968664 Mon Sep 17 00:00:00 2001 From: opakholis Date: Mon, 2 Mar 2026 03:37:07 +0000 Subject: [PATCH 23/45] Translated using Weblate (Indonesian) Currently translated at 98.3% (491 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/id/ --- app/src/main/res/values-in/strings.xml | 81 +++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 356732cd..23e81de6 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -242,8 +242,8 @@ Ukuran font Warna font Transparansi font - Jenis garis tepi - Warna garis tepi + Jenis pinggiran + Warna pinggiran Transparansi latar belakang Gaya latar belakang Gaya subtitel @@ -318,7 +318,7 @@ Membutuhkan PIN untuk profil PIN akan dihapus Opsi tampilan - Ukuran garis tepi + Ukuran pinggiran Otomatis Gunakan nama pengguna/kata sandi Batas tepi @@ -446,4 +446,79 @@ Preset bawaan untuk kustomisasi cepat semua baris Pilih baris dan gambar di halaman beranda Favoritkan %s + Artis + + %s menit + + Mulai setelah + Animasi + Rating usia maks + Tidak ada maks + Untuk semua umur + Maks. usia %s + Screensaver + Pengaturan screensaver + Mulai screensaver + Durasi + Foto + Sertakan jenis + Tampilkan gambar untuk jenis item + Wholphin Bawaan + Wholphin Ringkas + Gambar Thumbnail Serial + Gambar Thumbnail Episode + Terendah + Rendah + Sedang + Tinggi + Penuh + Ungu + Oranye + Biru Tegas + Hitam + Abaikan + Lewati otomatis + Tanya sebelum melewati + Gunakan FFmpeg hanya jika dekoder bawaan tidak ada + Utamakan FFmpeg daripada dekoder bawaan + Jangan pernah gunakan dekoder FFmpeg + Di akhir pemutaran + Selama kredit akhir/outro + Putih + Abu-abu muda + Abu-abu tua + Kuning + Sian + Magenta + Garis luar + Utamakan MPV + Gunakan ExoPlayer untuk pemutaran HDR + Poster (2:3) + 16:9 + 4:3 + Persegi (1:1) + Utama + Thumbnail + Gambar dengan warna dinamis + Hanya gambar + + API Key + Masuk ke server Seerr + Pengguna Jellyfin + Pengguna lokal + + Subtitel remote tidak ditemukan + Tayang + Gunakan screensaver dalam aplikasi + Aktor + Komposer + Penulis + Bintang tamu + Produser + Konduktor + Penulis lirik + Aransemen + Teknisi + Mixer + Kreator From d051b0d4025fa4a898db32a5b9be393102f40794 Mon Sep 17 00:00:00 2001 From: ymir Date: Mon, 2 Mar 2026 07:29:45 +0000 Subject: [PATCH 24/45] Translated using Weblate (French) Currently translated at 100.0% (499 of 499 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/fr/ --- app/src/main/res/values-fr/strings.xml | 85 ++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 538fd523..04a595ed 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -480,4 +480,89 @@ Préréglages intégrés pour styliser rapidement toutes les lignes Choisissez des lignes et des images sur la page d\'accueil Favori %s + + %s minute + %s minutes + %s minute + + Démarrer après + Animer + Classification par âge maximale + Pas de maximum + Pour tous les âges + Jusqu\'à l\'âge de %s + Écran de veille + Paramètres de l\'économiseur d\'écran + Lancer l\'économiseur d\'écran + Durée + Photos + Inclure les types + Quels types d\'éléments afficher des images pour + Ajustement + Recadrer + Remplir + Largeur de remplissage + Hauteur de remplissage + Wholphin Par défaut + Wholphin Compact + Images miniatures de la série + Images miniatures de l\'épisode + Le plus bas + Faible + Moyen + Haute + Complet + Violet + Orange + Bleu gras + Noir + Ignorer + Passer automatiquement + Demander à passer + N\'utilisez FFmpeg que si aucun décodeur intégré n\'existe + Préférez utiliser FFmpeg plutôt que les décodeurs intégrés + N\'utilisez jamais les décodeurs FFmpeg + À la fin de la lecture + Pendant le générique de fin/outro + Blanc + Gris clair + Gris foncé + Jaune + Cyan + Magenta + Contour + Ombre + Envelopper + Encadré + Préférer MPV + Utiliser ExoPlayer pour la lecture HDR + Affiche (2:3) + 16:9 + 4:3 + Carré (1:1) + Primaire + Miniature + Image avec couleur dynamique + Image uniquement + < ![CDATA[Entrez l\'URL et la clé API]]> + Clé API + Se connecter au serveur Seerr + Utilisateur Jellyfin + Utilisateur local + < ![CDATA[Rechercher et télécharger des sous-titres]]> + Aucun sous-titre distant n\'a été trouvé + Présent + Utiliser l\'économiseur d\'écran intégré + Acteur + Compositeur + Écrivain + Invité spécial + Producteur + Conducteur + Parolier + Arrangeur + Ingénieur + Mélangeur + Créateur + Artiste From d4cb9a6c9276c237d79213474360f8149a048ea7 Mon Sep 17 00:00:00 2001 From: danpergal84 Date: Tue, 3 Mar 2026 13:03:29 +0000 Subject: [PATCH 25/45] Translated using Weblate (Spanish) Currently translated at 100.0% (501 of 501 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a6340bc5..3ad0ffa1 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -565,4 +565,6 @@ Ingeniero de mezcla Creador Artista + ¿Estás seguro de que quieres eliminar este elemento? + Mostrar opciones de gestión de medios From 61369ffac883eb70cb931bd28b554aaf8f59e9a5 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 3 Mar 2026 11:21:43 +0000 Subject: [PATCH 26/45] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (501 of 501 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ad8008a2..fa30747e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -529,4 +529,6 @@ 混音 主创 艺人 + 显示媒体管理选项 + 是否确定要删除此项目? From 4ee8a5b4fcfbccfa595e1e97154461965b63444f Mon Sep 17 00:00:00 2001 From: idezentas Date: Tue, 3 Mar 2026 12:15:43 +0000 Subject: [PATCH 27/45] Translated using Weblate (Turkish) Currently translated at 100.0% (501 of 501 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/ --- app/src/main/res/values-tr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 636a6349..f08bed40 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -556,4 +556,6 @@ Karıştırıcı Yaratıcı Sanatçı + Bu öğeyi silmek istediğinizden emin misiniz? + Medya yönetim seçeneklerini göster From 000bd106228421f6a3af5cfe10e080846b79e11e Mon Sep 17 00:00:00 2001 From: danpergal84 Date: Tue, 3 Mar 2026 23:44:51 +0000 Subject: [PATCH 28/45] Translated using Weblate (Spanish) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3ad0ffa1..93bf5c7c 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -80,7 +80,7 @@ Crear nueva lista de reproducción Agregar a la lista de reproducción Pausar con un clic - Presiona el centro del D-Pad para pausar/reproducir + Pulsa el botón central de la cruceta para reproducir/pausar Éxito Duración Extras @@ -567,4 +567,5 @@ Artista ¿Estás seguro de que quieres eliminar este elemento? Mostrar opciones de gestión de medios + Buscar %s From 30095366703300e384968f60a9424387ff6f7928 Mon Sep 17 00:00:00 2001 From: SimonHung Date: Wed, 4 Mar 2026 02:38:45 +0000 Subject: [PATCH 29/45] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8a1478a9..5bf09d0f 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -529,4 +529,7 @@ 混音師 創作者 演出者 + 確定要刪除此項目嗎? + 顯示媒體管理選項 + 搜尋 %s From b0236cbd3b74598204bb1efe7375b918a9566ce4 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Wed, 4 Mar 2026 02:10:14 +0000 Subject: [PATCH 30/45] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fa30747e..47821481 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -531,4 +531,5 @@ 艺人 显示媒体管理选项 是否确定要删除此项目? + 搜索 %s From d69b6d6bb491725f8c848b22c64b5795ed2415a2 Mon Sep 17 00:00:00 2001 From: idezentas Date: Tue, 3 Mar 2026 20:52:19 +0000 Subject: [PATCH 31/45] Translated using Weblate (Turkish) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/ --- app/src/main/res/values-tr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f08bed40..a91c431b 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -558,4 +558,5 @@ Sanatçı Bu öğeyi silmek istediğinizden emin misiniz? Medya yönetim seçeneklerini göster + Ara %s From 0d326b49e750b7ddb06b54b1b43cf5800db986c7 Mon Sep 17 00:00:00 2001 From: zyplex Date: Thu, 5 Mar 2026 22:28:00 +0000 Subject: [PATCH 32/45] Translated using Weblate (German) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/ --- app/src/main/res/values-de/strings.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8076b47f..ffc2c399 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -446,4 +446,19 @@ kürzlich veröffentlicht in %1$s Höhe Auf alle Reihen anwenden + Reihe hinzufügen für %1$s + Überschreibe Einstellungen auf dem Server? + Überschreibe lokale Einstellungen? + Vorschläge für %1$s + Sende Media Info Log zum Server + Anpassung Homepage + Für Episoden + Vorlagen um schnell alle Reihen zu gestalten + Wähle Reihen und Bilder auf der Homepage + Starte anschließend + Serien Thumbnails + Episoden Thumbnail + Einloggen zum Seerr Server + Jellyfin User + Zeige Media Management Optionen From b6cef6a4b18960edc4924868369ea48e44a00070 Mon Sep 17 00:00:00 2001 From: North-DaCoder Date: Fri, 6 Mar 2026 12:49:17 +0000 Subject: [PATCH 33/45] Translated using Weblate (German) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/ --- app/src/main/res/values-de/strings.xml | 113 ++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ffc2c399..88899ee7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -199,12 +199,12 @@ Kursivschrift Laufzeit - Theme Songs - + Titelsong + Titelsongs - Theme Videos - + Titelvideo + Titelvideos Clip @@ -224,11 +224,11 @@ Shorts - + Kurzvideos Trifft nur auf Serien zu - Direct play ASS subtitles - Direct play PGS subtitles + Direktwiedergabe ASS Untertitel + Direktwiedergabe PGS Untertitel Immer zu Stereo heruntermischen FFmpeg decoder Modul verwenden Standard Skalierung @@ -251,8 +251,8 @@ Schriftgröße Schriftfarbe Schriftart Deckkraft - Rand Stil - Rand Farbe + Stil der Umrandung + Farbe der Umrandung Hintergrund Deckktraft Hintergrund Stil Untertitel Stil @@ -329,7 +329,7 @@ Login via Server Drücke die mittlere Taste zur Bestätigung Größe des Festplatten-Caches für Bilder (MB) - Besetzung & Mitwirkende + Container Codec Level @@ -456,9 +456,98 @@ Vorlagen um schnell alle Reihen zu gestalten Wähle Reihen und Bilder auf der Homepage Starte anschließend - Serien Thumbnails - Episoden Thumbnail + Serien Vorschaubilder + Episoden Vorschaubilder Einloggen zum Seerr Server Jellyfin User Zeige Media Management Optionen + Größe für alle Karten reduzieren + Einstellungen gespeichert + Maximale Altersfreigabe + Keine Begrenzung + Ohne Altersbeschränkung + Ab %s Jahre + Bildschirmschoner + Bildschirmschoner-Einstellungen + Bildschirmschoner starten + Länge + Fotos + Arten einschließen + Für welche Arten von Einträgen sollen Bilder angezeigt werden + Angepasst + Zugeschnitten + Ausgefüllt + Ignorieren + Automatisch überspringen + Vor dem Überspringen nachfragen + FFmpeg nur verwenden, sofern keine anderen Decoder vorhanden sind + FFmpeg statt der vorhandenen Decoder verwenden + FFmpeg Decoder nie verwenden + Am Ende einer Playlist + Während der Credits/Outro + Weiß + Hellgrau + Dunkelgrau + Gelb + Cyan + Magenta + Umrandung + Schatten + MPV bevorzugen + ExoPlayer für die HDR Wiedergabe verwenden + Poster (2:3) + 16:9 + 4:3 + Quadrat (1:1) + Primär + Bild mit dynamischen Farben + API Schlüssel + Lokaler Benutzer + Suche %s + Design-Vorlagen + Animieren + Breite ausfüllen + Höhe ausfüllen + Wholphin Standard + Wholphin Kompakt + Niedrigste + Niedrig + mittel + Hoch + Voll + Lila + Orange + Schwarz + Nur Bild + Aktuell + Eingebauten Bildschirmschoner nutzen + Bist du sicher, dass du diesen Eintrag löschen möchtest? + Schauspieler + Komponist + Autor + Nebenrolle + Produzent + Dirigent + Liedtext-Schreiber + Künstler + %s als Favorit speichern + Startseite - Reihen + Vom Server Benutzerprofil laden + Im Server Benutzerprofil speichern + Vom Webclient laden + Größe für alle Karten erhöhen + Vorschaubilder benutzen + Es wurden keine Untertitel gefunden + Arrangeur + Ingenieur + Dunkelblau + Untertitel-Transparenz + Vorschaubild + Serien Vorschaubilder verwenden + + + Ersteller + Gestreckt + Zentriert + Mischer From 0c197087742cecb11d0fc366cefda10f327dd964 Mon Sep 17 00:00:00 2001 From: isbit Date: Thu, 5 Mar 2026 16:40:15 +0000 Subject: [PATCH 34/45] Translated using Weblate (Swedish) Currently translated at 54.5% (274 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sv/ --- app/src/main/res/values-sv/strings.xml | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index b7d13882..cef8bb90 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -277,4 +277,49 @@ Skriv in PIN Logga in automatiskt Logga in via server + Ange server-adress + Nätverksfel + Okänt fel + + %s minut + %s minuter + + Fet stil + Dolby Atmos + Avbryt ändringar? + Kräv PIN för profilen + Upplösning + Språk + Ja + Nej + Upprepa + Lösenord + Användarnamn + Skapare + Ingenjör + Skådespelare + Kompositör + Författare + Producent + Endast bild + Vit + Ljusgrå + Mörkgrå + Gul + Lila + Svart + För alla åldrar + Upp till %s år + Skärmsläckare + Inställningar Skärmsläckare + Starta skärmsläckare + Bilder + Inställningar sparade + Rotera vänster + Rotera höger + Zooma in + Zooma ut + Ingen gräns + Lägg till rad + Tryck bakåt för att avbryta From b4c35ce41b4e33a2ba2f382bf1d6f3b6246e89ea Mon Sep 17 00:00:00 2001 From: arcker95 Date: Thu, 5 Mar 2026 18:59:20 +0000 Subject: [PATCH 35/45] Translated using Weblate (Italian) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2f22c8d4..3142001e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -565,4 +565,7 @@ Tecnico di mixaggio Artista Creatore + Cerca %s + Sei sicuro di voler cancellare questo elemento? + Mostra opzioni di gestione dei media From 062a3e99a4b166d7366d8f0585aa4f1466b9dc32 Mon Sep 17 00:00:00 2001 From: EmadAljohani Date: Thu, 5 Mar 2026 23:24:07 +0000 Subject: [PATCH 36/45] Translated using Weblate (Arabic) Currently translated at 23.9% (120 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/ar/ --- app/src/main/res/values-ar/strings.xml | 130 ++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 55344e51..327f969b 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -1,3 +1,131 @@ - \ No newline at end of file + حول + التسجيلات النشطة + إضافة للمفضلة + إضافة خادم + إضافة مستخدم + الصوت + مكان الميلاد + معدل البت + وُلد + إلغاء التسجيل + إلغاء تسجيل المسلسل + إلغاء + الفصول + اختيار %1$s + مسح ذاكرة التخزين المؤقت للصور + مجموعة + المجموعات + تجاري + تقييم المجتمع + حدث خطأ! اضغط على الزر لإرسال السجلات إلى خادمك. + تأكيد + تابع المشاهدة + تقييم النقاد + %.2f ثانية + الافتراضي + حذف + تُوفي + إخراج %1$s + المخرج + مُعطّل + الخوادم المكتشفة + + تنزيل… + مُفعّل + ينتهي عند %1$s + أدخل عنوان IP للخادم أو رابط URL + أدخل عنوان الخادم + الحلقات + خطأ في تحميل المجموعة %1$s + خارجي + المفضلة + الحجم + إجبارية + الأنواع + انتقل إلى المسلسل + انتقل إلى + إخفاء عناصر التحكم في التشغيل + إخفاء معلومات تصحيح الأخطاء + إخفاء + الرئيسية + فوري + المقدمة + #ABCDEFGHIJKLMNOPQRSTUVWXYZ + المكتبة + معلومات الترخيص + تلفزيون مباشر + تحميل… + هل تريد تعيين المسلسل بأكمله كمُشاهَد؟ + هل تريد تعيين المسلسل بأكمله كغير مشاهد؟ + تعيين كغير مشاهد + تعيين كمشاهد + الحد الأقصى لمعدل البت + المزيد من هذا القبيل + المزيد + + الأفلام + + + + + + + الاسم + التالي + لا توجد بيانات + لا توجد نتائج + لم يتم العثور على خوادم + لا توجد تسجيلات مجدولة + لا يوجد تحديث متاح + لا شيء + الترجمات الإجبارية فقط + خاتمة + المسار + + الأشخاص + + + + + + + عدد مرات التشغيل + تشغيل من هنا + تشغيل + إظهار معلومات التشغيل التفصيلية + سرعة التشغيل + التشغيل + قائمة التشغيل + قوائم التشغيل + معاينة + إعدادات ملف تعريف المستخدم + قائمة الانتظار + ملخص ما سبق + أُضيف مؤخرًا في %1$s + أُضيف مؤخرًا + التسجيلات الأخيرة + أحدث الإصدارات + مقترح + تسجيل البرنامج + تسجيل المسلسل + إزالة من المفضلة + إعادة تشغيل + استئناف + حفظ + + بحث + جارٍ البحث… + البحث الصوتي + جارٍ البدء + تحدث للبحث + جارٍ المعالجة + اضغط رجوع للإلغاء + خطأ في تسجيل الصوت + خطأ في التعرف على الصوت + مطلوب إذن الوصول للميكروفون + خطأ في الشبكة + انتهت مهلة الشبكة + لم يتم التعرف على الكلام + From d0f79086482c8e4ecd4d0e677c768539039497c2 Mon Sep 17 00:00:00 2001 From: n8llcaster Date: Fri, 6 Mar 2026 13:35:18 +0000 Subject: [PATCH 37/45] Translated using Weblate (Slovak) Currently translated at 44.6% (224 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sk/ --- app/src/main/res/values-sk/strings.xml | 78 +++++++++++++------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 195565df..dfb498ba 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -146,9 +146,9 @@ Trailer Trailery - - - + + + DVR rozvrh Sprievodca @@ -156,9 +156,9 @@ Série Seriály - - - + + + Rozhranie Neznámy @@ -186,69 +186,69 @@ Bonusový materiál Ostatné - - - + + + Zo zákulisia - - - + + + Tématické piesne - - - + + + Tématické videá - - - + + + Klipy - - - + + + Vymazané scény - - - + + + Rozhovory - - - + + + Scény - - - + + + Vzorky - - - + + + Krátkometrážne filmy - - - + + + Krátke videá - - - + + + Obľúbené %s From 95e0725d8d3a7341a911d9ba01a3553b5a2dd4be Mon Sep 17 00:00:00 2001 From: RafaelSoaresP Date: Fri, 6 Mar 2026 13:35:17 +0000 Subject: [PATCH 38/45] Translated using Weblate (Portuguese (Brazil)) Currently translated at 76.8% (386 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt_BR/ --- app/src/main/res/values-pt-rBR/strings.xml | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 76bf308f..ed794d85 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -65,8 +65,8 @@ Mais Filmes - - + + Nome A Seguir @@ -81,8 +81,8 @@ Caminho Pessoas - - + + Contagem de Reproduções Reproduzir a partir daqui @@ -152,8 +152,8 @@ Trailer Trailers - - + + Agenda do DVR Guia @@ -161,8 +161,8 @@ Temporadas Programas de TV - - + + Interface Desconhecido @@ -190,53 +190,53 @@ Extras Outro - - + + Por Trás das Cenas - - + + Músicas Tema - - + + Vídeos Tema - - + + Clipes - - + + Cenas Deletadas - - + + Entrevistas - - + + Cenas - - + + Amostras - - + + Curtas - - + + Favoritar %s From dd0f253524d1e1a37a2002267603d15196cef3f3 Mon Sep 17 00:00:00 2001 From: North-DaCoder Date: Fri, 6 Mar 2026 15:18:56 +0000 Subject: [PATCH 39/45] Translated using Weblate (German) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/ --- app/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 88899ee7..2a8884ba 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -227,7 +227,7 @@ Kurzvideos Trifft nur auf Serien zu - Direktwiedergabe ASS Untertitel + Libass für ASS Untertitel verwenden Direktwiedergabe PGS Untertitel Immer zu Stereo heruntermischen FFmpeg decoder Modul verwenden From b3be20f6dad2760d5363c78a1771673ca140a7d5 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Fri, 6 Mar 2026 15:30:30 +0000 Subject: [PATCH 40/45] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 47821481..3134076d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -208,7 +208,7 @@ 检查更新 仅适用于电视剧 - 直接播放 ASS 字幕 + 使用 libass 处理 ASS 字幕 直接播放 PGS 字幕 始终降混为立体声 使用 FFmpeg 解码模块 From 4cfb471e5da6f904c89df6638fdae51c36b0fb7f Mon Sep 17 00:00:00 2001 From: idezentas Date: Fri, 6 Mar 2026 15:16:02 +0000 Subject: [PATCH 41/45] Translated using Weblate (Turkish) Currently translated at 100.0% (502 of 502 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/ --- app/src/main/res/values-tr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a91c431b..213ef136 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -256,7 +256,7 @@ Güncellemeleri kontrol et Yalnızca diziler için geçerlidir - ASS altyazıları doğrudan oynat + ASS altyazıları için libass kullan PGS altyazıları doğrudan oynat Her zaman Stereo\'ya dönüştür FFmpeg kod çözücü kullan From 73cf54b844e06491036ed327c1118092e29a0d89 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Fri, 6 Mar 2026 10:49:06 -0500 Subject: [PATCH 42/45] Fixes to strings --- app/src/main/res/values-fr/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 04a595ed..74e702c8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -221,8 +221,8 @@ Opacité de la police Style des contours Couleur des contours - Opacité de l’arrière-plan - Style de l’arrière-plan + Opacité de l\'arrière-plan + Style de l\'arrière-plan Style des sous-titres Couleur de fond Réinitialiser @@ -234,10 +234,10 @@ Options ExoPlayer Passer le segment Passer les publicités - Passer l’aperçu + Passer l\'aperçu Passer le récapitulatif Passer le générique de fin - Passer l’intro + Passer l\'intro Lu Filtre Année @@ -275,7 +275,7 @@ Lire la musique de thème Revenir en arrière à la reprise de la lecture Comportement de saut des publicités - Comportement de saut de l’intro + Comportement de saut de l\'intro Comportement de saut du générique de fin Comportement de saut des aperçus Comportement de saut du récapitulatif @@ -289,7 +289,7 @@ - + Calendrier du DVR @@ -544,12 +544,12 @@ Miniature Image avec couleur dynamique Image uniquement - < ![CDATA[Entrez l\'URL et la clé API]]> + Entrez l\'URL et la clé API Clé API Se connecter au serveur Seerr Utilisateur Jellyfin Utilisateur local - < ![CDATA[Rechercher et télécharger des sous-titres]]> + Rechercher et télécharger des sous-titres Aucun sous-titre distant n\'a été trouvé Présent Utiliser l\'économiseur d\'écran intégré From 8a4be3dd32040f577c9486ca339c1c57efcfa744 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Fri, 6 Mar 2026 16:37:06 -0500 Subject: [PATCH 43/45] Release v0.5.2 From 256bb3f9884016fd18510fa64b34f1160e97bff0 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:16:35 -0500 Subject: [PATCH 44/45] Fix crash after last screensaver image in list (#1055) ## Description Fix a screensaver crash after the last item Also adds some extra error checking ### Related issues Fixes #1053 ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None --- .../wholphin/services/AppUpgradeHandler.kt | 2 +- .../wholphin/services/ScreensaverService.kt | 58 ++++++++++--------- 2 files changed, 32 insertions(+), 28 deletions(-) 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 23d6cef6..0331f04b 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 @@ -250,7 +250,7 @@ suspend fun upgradeApp( } } - if (previous.isEqualOrBefore(Version.fromString("0.5.0-6-g0"))) { + if (previous.isEqualOrBefore(Version.fromString("0.5.3-0-g0"))) { appPreferences.updateData { it.updateScreensaverPreferences { startDelay = ScreensaverPreference.DEFAULT_START_DELAY diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt index 5d73ec21..c81893b3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt @@ -161,27 +161,32 @@ class ScreensaverService if (pager.isEmpty()) { emit(null) } else { + val duration = + userPreferencesService + .getCurrent() + .appPreferences + .interfacePreferences.screensaverPreference.duration.milliseconds while (true) { - val item = pager.getBlocking(index) - Timber.v("Next index=%s, item=%s", index, item?.id) - if (item != null) { - val backdropUrl = - if (item.type == BaseItemKind.PHOTO) { - api.libraryApi.getDownloadUrl(item.id) - } else { - imageUrlService.getItemImageUrl(item, ImageType.BACKDROP) - } - val title = - if (item.type == BaseItemKind.PHOTO) { - item.data.premiereDate?.let { - formatDate(it.toLocalDate()) + try { + val item = pager.getBlocking(index) + Timber.v("Next index=%s, item=%s", index, item?.id) + if (item != null) { + val backdropUrl = + if (item.type == BaseItemKind.PHOTO) { + api.libraryApi.getDownloadUrl(item.id) + } else { + imageUrlService.getItemImageUrl(item, ImageType.BACKDROP) } - } else { - item.title - } - val logoUrl = imageUrlService.getItemImageUrl(item, ImageType.LOGO) - if (backdropUrl != null) { - try { + val title = + if (item.type == BaseItemKind.PHOTO) { + item.data.premiereDate?.let { + formatDate(it.toLocalDate()) + } + } else { + item.title + } + val logoUrl = imageUrlService.getItemImageUrl(item, ImageType.LOGO) + if (backdropUrl != null) { context.imageLoader .enqueue( ImageRequest @@ -191,18 +196,17 @@ class ScreensaverService ).job .await() emit(CurrentItem(item, backdropUrl, logoUrl, title ?: "")) - } catch (_: CancellationException) { - break + delay(duration) } - val duration = - userPreferencesService - .getCurrent() - .appPreferences - .interfacePreferences.screensaverPreference.duration.milliseconds - delay(duration) } + } catch (_: CancellationException) { + break + } catch (ex: Exception) { + Timber.e(ex, "Error fetching next item") + delay(duration) } index++ + if (index > pager.lastIndex) index = 0 } } }.flowOn(Dispatchers.Default).cancellable() From 49a6cd8d2f3fe84b79b0d02358af50e0a922ec1e Mon Sep 17 00:00:00 2001 From: Damontecres Date: Fri, 6 Mar 2026 18:16:48 -0500 Subject: [PATCH 45/45] Release v0.5.3