Skip to content

Commit

Permalink
feat(player): Add better auto sub select (#1706)
Browse files Browse the repository at this point in the history
Co-authored-by: Abdallah <[email protected]>
Co-authored-by: jmir1 <[email protected]>
  • Loading branch information
3 people committed Jul 18, 2024
1 parent 58817c7 commit 291c24b
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 100 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package eu.kanade.presentation.more.settings

import android.content.Context
import android.os.Build
import android.os.Environment
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector
import eu.kanade.core.preference.asState
import eu.kanade.tachiyomi.data.track.Tracker
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.coroutines.CoroutineScope
import tachiyomi.core.common.storage.openFileDescriptor
import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.FileOutputStream
import tachiyomi.core.common.preference.Preference as PreferenceData

sealed class Preference {
Expand Down Expand Up @@ -151,6 +161,44 @@ sealed class Preference {
val canBeBlank: Boolean = false,
) : PreferenceItem<String>()

/**
* A [PreferenceItem] for editing MPV config files.
* If [fileName] is not null, it will update this file in the config directory.
*/
data class MPVConfPreference(
val pref: PreferenceData<String>,
val scope: CoroutineScope,
val context: Context,
val fileName: String? = null,
override val title: String,
override val subtitle: String? = pref.asState(scope).value
.lines().take(2)
.joinToString(
separator = "\n",
postfix = if (pref.asState(scope).value.lines().size > 2) "\n..." else "",
),
override val icon: ImageVector? = null,
override val enabled: Boolean = true,
override val onValueChanged: suspend (newValue: String) -> Boolean = { newValue ->
if (fileName != null) {
val storageManager: StorageManager = Injekt.get()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
val inputFile = storageManager.getMPVConfigDirectory()
?.createFile(fileName)
inputFile?.openFileDescriptor(context, "rwt")?.fileDescriptor
?.let {
FileOutputStream(it).bufferedWriter().use { writer ->
writer.write(newValue)
}
}
pref.set(newValue)
}
}
true
},
val canBeBlank: Boolean = true,
) : PreferenceItem<String>()

/**
* A [PreferenceItem] for individual tracker.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,22 @@ internal fun PreferenceItem(
canBeBlank = item.canBeBlank,
)
}
is Preference.PreferenceItem.MPVConfPreference -> {
val values by item.pref.collectAsState()
EditTextPreferenceWidget(
title = item.title,
subtitle = item.subtitle,
icon = item.icon,
value = values,
onConfirm = {
val accepted = item.onValueChanged(it)
if (accepted) item.pref.set(it)
accepted
},
singleLine = false,
canBeBlank = item.canBeBlank,
)
}
is Preference.PreferenceItem.TrackerPreference -> {
val isLoggedIn by item.tracker.let { tracker ->
tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package eu.kanade.presentation.more.settings.screen

import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Build
Expand All @@ -10,13 +9,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import eu.kanade.core.preference.asState
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.player.viewer.VideoDebanding
import kotlinx.collections.immutable.toImmutableMap
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
Expand All @@ -25,60 +22,35 @@ object AdvancedPlayerSettingsScreen : SearchableSettings {
@Composable
override fun getTitleRes() = MR.strings.pref_category_player_advanced

@SuppressLint("InlinedApi")
@Composable
override fun getPreferences(): List<Preference> {
val playerPreferences = remember { Injekt.get<PlayerPreferences>() }
val scope = rememberCoroutineScope()
val context = LocalContext.current
val mpvConf = playerPreferences.mpvConf()
val mpvInput = playerPreferences.mpvInput()
val storageManager: StorageManager = Injekt.get()
val subSelectConf = playerPreferences.subSelectConf()

return listOf(
Preference.PreferenceItem.MultiLineEditTextPreference(
Preference.PreferenceItem.MPVConfPreference(
pref = mpvConf,
title = context.stringResource(MR.strings.pref_mpv_conf),
subtitle = mpvConf.asState(scope).value
.lines().take(2)
.joinToString(
separator = "\n",
postfix = if (mpvConf.asState(scope).value.lines().size > 2) "\n..." else "",
),
onValueChanged = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
val inputFile = storageManager.getMPVConfigDirectory()
?.createFile("mpv.conf")
inputFile?.openOutputStream()?.bufferedWriter().use { writer ->
writer?.write(it)
}
mpvConf.set(it)
}
true
},
canBeBlank = true,
fileName = "mpv.conf",
scope = scope,
context = context,
),
Preference.PreferenceItem.MultiLineEditTextPreference(
Preference.PreferenceItem.MPVConfPreference(
pref = mpvInput,
title = context.stringResource(MR.strings.pref_mpv_input),
subtitle = mpvInput.asState(scope).value
.lines().take(2)
.joinToString(
separator = "\n",
postfix = if (mpvInput.asState(scope).value.lines().size > 2) "\n..." else "",
),
onValueChanged = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
val inputFile = storageManager.getMPVConfigDirectory()
?.createFile("input.conf")
inputFile?.openOutputStream()?.bufferedWriter().use { writer ->
writer?.write(it)
}
mpvInput.set(it)
}
true
},
canBeBlank = true,
fileName = "input.conf",
scope = scope,
context = context,
),
Preference.PreferenceItem.MPVConfPreference(
pref = subSelectConf,
title = context.stringResource(MR.strings.pref_sub_select_conf),
scope = scope,
context = context,
),
Preference.PreferenceItem.SwitchPreference(
title = context.stringResource(MR.strings.pref_gpu_next_title),
Expand Down
143 changes: 87 additions & 56 deletions app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import eu.kanade.tachiyomi.ui.player.viewer.VideoDebanding
import eu.kanade.tachiyomi.util.AniSkipApi
import eu.kanade.tachiyomi.util.SkipType
import eu.kanade.tachiyomi.util.Stamp
import eu.kanade.tachiyomi.util.SubtitleSelect
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.powerManager
import eu.kanade.tachiyomi.util.system.toShareIntent
Expand Down Expand Up @@ -1738,50 +1739,111 @@ class PlayerActivity : BaseActivity() {
}
}

private val subtitleSelect = SubtitleSelect(playerPreferences)

private fun selectSubtitle(subtitleTracks: List<Track>, index: Int, embedded: Boolean = false) {
val offset = if (embedded) 0 else 1
streams.subtitle.index = index + offset
val tracks = player.tracks.getValue("sub")
val selectedLoadedTrack = tracks.firstOrNull {
it.name == subtitleTracks[index].url ||
it.mpvId.toString() == subtitleTracks[index].url
}
selectedLoadedTrack?.let { player.sid = it.mpvId }
?: MPVLib.command(
arrayOf(
"sub-add",
subtitleTracks[index].url,
"select",
subtitleTracks[index].url,
),
)
}

// TODO: exception java.util.ConcurrentModificationException:
// UPDATE: MAY HAVE BEEN FIXED
// at java.lang.Object java.util.ArrayList$Itr.next() (ArrayList.java:860)
// at void eu.kanade.tachiyomi.ui.player.PlayerActivity.fileLoaded() (PlayerActivity.kt:1874)
// at void eu.kanade.tachiyomi.ui.player.PlayerActivity.event(int) (PlayerActivity.kt:1566)
// at void is.xyz.mpv.MPVLib.event(int) (MPVLib.java:86)
@SuppressLint("SourceLockedOrientationActivity")
internal suspend fun fileLoaded() {
setMpvMediaTitle()
val localLangName = LocaleHelper.getSimpleLocaleDisplayName()
clearTracks()
player.loadTracks()
setupSubtitleTracks()
setupAudioTracks()

viewModel.viewModelScope.launchUI {
if (playerPreferences.adjustOrientationVideoDimensions().get()) {
if ((player.videoW ?: 1) / (player.videoH ?: 1) >= 1) {
this@PlayerActivity.requestedOrientation =
playerPreferences.defaultPlayerOrientationLandscape().get()

switchControlsOrientation(true)
} else {
this@PlayerActivity.requestedOrientation =
playerPreferences.defaultPlayerOrientationPortrait().get()

switchControlsOrientation(false)
}
}

viewModel.mutableState.update {
it.copy(isLoadingEpisode = false)
}
}
// aniSkip stuff
waitingAniSkip = playerPreferences.waitingTimeAniSkip().get()
runBlocking {
if (aniSkipEnable) {
aniSkipInterval = viewModel.aniSkipResponse(player.duration)
aniSkipInterval?.let {
aniskipStamps = it
updateChapters(it, player.duration)
}
}
}
}

private fun setupSubtitleTracks() {
streams.subtitle.tracks += player.tracks.getOrElse("sub") { emptyList() }
.drop(1).map { track ->
Track(track.mpvId.toString(), track.name)
}.toTypedArray()
streams.audio.tracks += player.tracks.getOrElse("audio") { emptyList() }
.drop(1).map { track ->
Track(track.mpvId.toString(), track.name)
}.toTypedArray()
if (hadPreviousSubs) {
streams.subtitle.tracks.getOrNull(streams.subtitle.index)?.let { sub ->
MPVLib.command(arrayOf("sub-add", sub.url, "select", sub.url))
}
} else {
currentVideoList?.getOrNull(streams.quality.index)
?.subtitleTracks?.let { tracks ->
val langIndex = tracks.indexOfFirst {
it.lang.contains(localLangName, true)
}
val requestedLanguage = if (langIndex == -1) 0 else langIndex
tracks.getOrNull(requestedLanguage)?.let { sub ->
hadPreviousSubs = true
streams.subtitle.index = requestedLanguage + 1
MPVLib.command(arrayOf("sub-add", sub.url, "select", sub.url))
}
} ?: run {
val mpvSub = player.tracks.getOrElse("sub") { emptyList() }
.firstOrNull { player.sid == it.mpvId }
streams.subtitle.index = mpvSub?.let {
streams.subtitle.tracks.indexOfFirst { it.url == mpvSub.mpvId.toString() }
}?.coerceAtLeast(0) ?: 0
}
return
}
val subtitleTracks = currentVideoList?.getOrNull(streams.quality.index)
?.subtitleTracks?.takeIf { it.isNotEmpty() }

subtitleTracks?.let { tracks ->
val preferredIndex = subtitleSelect.getPreferredSubtitleIndex(tracks) ?: 0
hadPreviousSubs = true
selectSubtitle(tracks, preferredIndex)
} ?: let {
val tracks = streams.subtitle.tracks.toList()
val preferredIndex = subtitleSelect.getPreferredSubtitleIndex(tracks)
?: let {
val mpvSub = player.tracks["sub"]?.firstOrNull { player.sid == it.mpvId }
mpvSub?.let {
streams.subtitle.tracks.indexOfFirst { it.url == mpvSub.mpvId.toString() }
}?.coerceAtLeast(0) ?: 0
}
selectSubtitle(tracks, preferredIndex, embedded = true)
}
}

private fun setupAudioTracks() {
val localLangName = LocaleHelper.getSimpleLocaleDisplayName()

streams.audio.tracks += player.tracks.getOrElse("audio") { emptyList() }
.drop(1).map { track ->
Track(track.mpvId.toString(), track.name)
}.toTypedArray()

if (hadPreviousAudio) {
streams.audio.tracks.getOrNull(streams.audio.index)?.let { audio ->
MPVLib.command(arrayOf("audio-add", audio.url, "select", audio.url))
Expand All @@ -1806,37 +1868,6 @@ class PlayerActivity : BaseActivity() {
}?.coerceAtLeast(0) ?: 0
}
}

viewModel.viewModelScope.launchUI {
if (playerPreferences.adjustOrientationVideoDimensions().get()) {
if ((player.videoW ?: 1) / (player.videoH ?: 1) >= 1) {
this@PlayerActivity.requestedOrientation =
playerPreferences.defaultPlayerOrientationLandscape().get()

switchControlsOrientation(true)
} else {
this@PlayerActivity.requestedOrientation =
playerPreferences.defaultPlayerOrientationPortrait().get()

switchControlsOrientation(false)
}
}

viewModel.mutableState.update {
it.copy(isLoadingEpisode = false)
}
}
// aniSkip stuff
waitingAniSkip = playerPreferences.waitingTimeAniSkip().get()
runBlocking {
if (aniSkipEnable) {
aniSkipInterval = viewModel.aniSkipResponse(player.duration)
aniSkipInterval?.let {
aniskipStamps = it
updateChapters(it, player.duration)
}
}
}
}

private fun setMpvMediaTitle() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class PlayerPreferences(

fun mpvInput() = preferenceStore.getString("pref_mpv_input", "")

fun subSelectConf() = preferenceStore.getString("pref_sub_select_conf", "")

fun defaultPlayerOrientationType() = preferenceStore.getInt(
"pref_default_player_orientation_type_key",
10,
Expand Down
Loading

0 comments on commit 291c24b

Please sign in to comment.