subtitle auto encoding and fixed auto subtitles

This commit is contained in:
LagradOst 2022-05-30 14:13:11 +02:00
parent 4b185019fe
commit 4701def2be
6 changed files with 106 additions and 45 deletions

View file

@ -171,4 +171,7 @@ dependencies {
implementation 'com.facebook.shimmer:shimmer:0.5.0' implementation 'com.facebook.shimmer:shimmer:0.5.0'
implementation "androidx.tvprovider:tvprovider:1.0.0" implementation "androidx.tvprovider:tvprovider:1.0.0"
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
} }

View file

@ -394,6 +394,7 @@ abstract class AbstractPlayerFragment(
override fun onDestroy() { override fun onDestroy() {
playerEventListener = null playerEventListener = null
keyEventListener = null keyEventListener = null
canEnterPipMode = false
SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged
keepScreenOn(false) keepScreenOn(false)

View file

@ -12,12 +12,14 @@ import com.google.android.exoplayer2.text.ttml.TtmlDecoder
import com.google.android.exoplayer2.text.webvtt.WebvttDecoder import com.google.android.exoplayer2.text.webvtt.WebvttDecoder
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.MimeTypes
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import org.mozilla.universalchardet.UniversalDetector
import java.nio.ByteBuffer import java.nio.ByteBuffer
class CustomDecoder : SubtitleDecoder { class CustomDecoder : SubtitleDecoder {
companion object { companion object {
private const val UTF_8 = "UTF-8"
private const val TAG = "CustomDecoder" private const val TAG = "CustomDecoder"
private var overrideEncoding: String? = null //TODO MAKE SETTING
var regexSubtitlesToRemoveCaptions = false var regexSubtitlesToRemoveCaptions = false
val bloatRegex = val bloatRegex =
listOf( listOf(
@ -65,18 +67,64 @@ class CustomDecoder : SubtitleDecoder {
if (realDecoder == null) { if (realDecoder == null) {
inputBuffer.data?.let { data -> inputBuffer.data?.let { data ->
// this way we read the subtitle file and decide what decoder to use instead of relying on mimetype // this way we read the subtitle file and decide what decoder to use instead of relying on mimetype
Log.i(TAG, "Got data from queueInputBuffer")
val pos = data.position() var str = try {
data.position(0) data.position(0)
val arr = ByteArray(minOf(data.remaining(), 100)) val fullDataArr = ByteArray(data.remaining())
data.get(arr) data.get(fullDataArr)
data.position(pos) val encoding = try {
val encoding = overrideEncoding ?: run {
val detector = UniversalDetector()
detector.handleData(fullDataArr, 0, fullDataArr.size)
detector.dataEnd()
detector.detectedCharset // "windows-1256" adabic
}
Log.i(
TAG,
"Detected encoding with charset $encoding and override = $overrideEncoding"
)
encoding ?: UTF_8
} catch (e: Exception) {
Log.e(TAG, "Failed to detect encoding throwing error")
logError(e)
UTF_8
}
var fullStr = try {
String(fullDataArr, charset(encoding))
} catch (e: Exception) {
Log.e(TAG, "Failed to parse using encoding $encoding")
logError(e)
fullDataArr.decodeToString()
}
Log.i(
TAG,
"Encoded Text start: " + fullStr.substring(
0,
minOf(fullStr.length, 300)
)
)
bloatRegex.forEach { rgx ->
fullStr = fullStr.replace(rgx, "\n")
}
fullStr.replace(Regex("(\r\n|\r|\n){2,}"), "\n")
fullStr
} catch (e: Exception) {
Log.e(TAG, "Failed to parse text returning plain data")
logError(e)
return
}
//https://emptycharacter.com/ //https://emptycharacter.com/
//https://www.fileformat.info/info/unicode/char/200b/index.htm //https://www.fileformat.info/info/unicode/char/200b/index.htm
val str = trimStr(arr.decodeToString()) //val str = trimStr(arr.decodeToString())
Log.i(TAG, "Got data from queueInputBuffer") //Log.i(TAG, "first string is >>>$str<<<")
Log.i(TAG, "first string is >>>$str<<<")
if (str.isNotEmpty()) { if (str.isNotEmpty()) {
//https://github.com/LagradOst/CloudStream-2/blob/ddd774ee66810137ff7bd65dae70bcf3ba2d2489/CloudStreamForms/CloudStreamForms/Script/MainChrome.cs#L388 //https://github.com/LagradOst/CloudStream-2/blob/ddd774ee66810137ff7bd65dae70bcf3ba2d2489/CloudStreamForms/CloudStreamForms/Script/MainChrome.cs#L388
realDecoder = when { realDecoder = when {
@ -93,33 +141,21 @@ class CustomDecoder : SubtitleDecoder {
TAG, TAG,
"Decoder selected: $realDecoder" "Decoder selected: $realDecoder"
) )
val decoder = realDecoder realDecoder?.let { decoder ->
if (decoder != null) {
decoder.dequeueInputBuffer()?.let { buff -> decoder.dequeueInputBuffer()?.let { buff ->
if (regexSubtitlesToRemoveCaptions && decoder::class.java != SsaDecoder::class.java) { if (regexSubtitlesToRemoveCaptions && decoder::class.java != SsaDecoder::class.java) {
try {
data.position(0)
val fullDataArr = ByteArray(data.remaining())
data.get(fullDataArr)
var fullStr = trimStr(fullDataArr.decodeToString())
bloatRegex.forEach { rgx ->
fullStr = fullStr.replace(rgx, "\n")
}
captionRegex.forEach { rgx -> captionRegex.forEach { rgx ->
fullStr = fullStr.replace(rgx, "\n") str = str.replace(rgx, "\n")
}
} }
fullStr.replace(Regex("(\r\n|\r|\n){2,}"), "\n")
buff.data = ByteBuffer.wrap(fullStr.toByteArray()) buff.data = ByteBuffer.wrap(str.toByteArray())
} catch (e: Exception) {
data.position(pos)
buff.data = data
}
} else {
buff.data = data
}
decoder.queueInputBuffer(buff) decoder.queueInputBuffer(buff)
Log.i(
TAG,
"Decoder queueInputBuffer successfully"
)
} }
CS3IPlayer.requestSubtitleUpdate?.invoke() CS3IPlayer.requestSubtitleUpdate?.invoke()
} }

View file

@ -477,15 +477,24 @@ class GeneratorPlayer : FullScreenPlayer() {
): SubtitleData? { ): SubtitleData? {
val langCode = preferredAutoSelectSubtitles ?: return null val langCode = preferredAutoSelectSubtitles ?: return null
val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null
if (downloads) {
return subtitles.firstOrNull { sub ->
(sub.origin == SubtitleOrigin.DOWNLOADED_FILE && sub.name == context?.getString(
R.string.default_subtitles
))
}
}
if (settings) sortSubs(subtitles).firstOrNull { sub ->
subtitles.firstOrNull { sub ->
val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim() val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim()
t == lang || t.startsWith("$lang ") (settings || (downloads && sub.origin == SubtitleOrigin.DOWNLOADED_FILE)) && t == lang || t.startsWith(
|| t == langCode "$lang "
) || t == langCode
}?.let { sub -> }?.let { sub ->
return sub return sub
} }
// post check in case both did not catch anything
if (downloads) { if (downloads) {
return subtitles.firstOrNull { sub -> return subtitles.firstOrNull { sub ->
(sub.origin == SubtitleOrigin.DOWNLOADED_FILE || sub.name == context?.getString( (sub.origin == SubtitleOrigin.DOWNLOADED_FILE || sub.name == context?.getString(
@ -493,10 +502,11 @@ class GeneratorPlayer : FullScreenPlayer() {
)) ))
} }
} }
return null return null
} }
private fun autoSelectFromSettings() { private fun autoSelectFromSettings(): Boolean {
// auto select subtitle based of settings // auto select subtitle based of settings
val langCode = preferredAutoSelectSubtitles val langCode = preferredAutoSelectSubtitles
@ -506,31 +516,36 @@ class GeneratorPlayer : FullScreenPlayer() {
if (setSubtitles(sub)) { if (setSubtitles(sub)) {
player.reloadPlayer(ctx) player.reloadPlayer(ctx)
player.handleEvent(CSPlayerEvent.Play) player.handleEvent(CSPlayerEvent.Play)
return true
} }
} }
} }
} }
return false
} }
private fun autoSelectFromDownloads() { private fun autoSelectFromDownloads(): Boolean {
if (player.getCurrentPreferredSubtitle() == null) { if (player.getCurrentPreferredSubtitle() == null) {
getAutoSelectSubtitle(currentSubs, settings = false, downloads = true)?.let { sub -> getAutoSelectSubtitle(currentSubs, settings = false, downloads = true)?.let { sub ->
context?.let { ctx -> context?.let { ctx ->
if (setSubtitles(sub)) { if (setSubtitles(sub)) {
player.reloadPlayer(ctx) player.reloadPlayer(ctx)
player.handleEvent(CSPlayerEvent.Play) player.handleEvent(CSPlayerEvent.Play)
return true
} }
} }
} }
} }
return false
} }
private fun autoSelectSubtitles() { private fun autoSelectSubtitles() {
normalSafeApiCall { normalSafeApiCall {
autoSelectFromSettings() if (!autoSelectFromSettings()) {
autoSelectFromDownloads() autoSelectFromDownloads()
} }
} }
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun setTitle() { fun setTitle() {
@ -567,7 +582,11 @@ class GeneratorPlayer : FullScreenPlayer() {
if (season == null) if (season == null)
" - ${ctx.getString(R.string.episode)} $episode" " - ${ctx.getString(R.string.episode)} $episode"
else else
" \"${ctx.getString(R.string.season_short)}${season}:${ctx.getString(R.string.episode_short)}${episode}\"" " \"${ctx.getString(R.string.season_short)}${season}:${
ctx.getString(
R.string.episode_short
)
}${episode}\""
else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName" else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName"
} else { } else {
"" ""

View file

@ -61,6 +61,7 @@ enum class Qualities(var value: Int) {
0 -> "Auto" 0 -> "Auto"
Unknown.value -> "" Unknown.value -> ""
P2160.value -> "4K" P2160.value -> "4K"
null -> ""
else -> "${qual}p" else -> "${qual}p"
} }
} }

View file

@ -4,4 +4,5 @@
style="@style/CheckLabel" style="@style/CheckLabel"
android:id="@android:id/text1" android:id="@android:id/text1"
tools:text="hello" tools:text="hello"
app:drawableStartCompat="@drawable/ic_baseline_add_24" /> app:drawableStartCompat="@drawable/ic_baseline_add_24"
app:drawableTint="?attr/textColor" />