forked from recloudstream/cloudstream
subtitle auto encoding and fixed auto subtitles
This commit is contained in:
parent
4b185019fe
commit
4701def2be
6 changed files with 106 additions and 45 deletions
|
@ -171,4 +171,7 @@ dependencies {
|
|||
implementation 'com.facebook.shimmer:shimmer:0.5.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'
|
||||
}
|
|
@ -394,6 +394,7 @@ abstract class AbstractPlayerFragment(
|
|||
override fun onDestroy() {
|
||||
playerEventListener = null
|
||||
keyEventListener = null
|
||||
canEnterPipMode = false
|
||||
SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged
|
||||
|
||||
keepScreenOn(false)
|
||||
|
|
|
@ -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.util.MimeTypes
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import org.mozilla.universalchardet.UniversalDetector
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
|
||||
class CustomDecoder : SubtitleDecoder {
|
||||
companion object {
|
||||
private const val UTF_8 = "UTF-8"
|
||||
private const val TAG = "CustomDecoder"
|
||||
private var overrideEncoding: String? = null //TODO MAKE SETTING
|
||||
var regexSubtitlesToRemoveCaptions = false
|
||||
val bloatRegex =
|
||||
listOf(
|
||||
|
@ -65,18 +67,64 @@ class CustomDecoder : SubtitleDecoder {
|
|||
if (realDecoder == null) {
|
||||
inputBuffer.data?.let { data ->
|
||||
// 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()
|
||||
data.position(0)
|
||||
val arr = ByteArray(minOf(data.remaining(), 100))
|
||||
data.get(arr)
|
||||
data.position(pos)
|
||||
var str = try {
|
||||
data.position(0)
|
||||
val fullDataArr = ByteArray(data.remaining())
|
||||
data.get(fullDataArr)
|
||||
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://www.fileformat.info/info/unicode/char/200b/index.htm
|
||||
val str = trimStr(arr.decodeToString())
|
||||
Log.i(TAG, "Got data from queueInputBuffer")
|
||||
Log.i(TAG, "first string is >>>$str<<<")
|
||||
//val str = trimStr(arr.decodeToString())
|
||||
//Log.i(TAG, "first string is >>>$str<<<")
|
||||
if (str.isNotEmpty()) {
|
||||
//https://github.com/LagradOst/CloudStream-2/blob/ddd774ee66810137ff7bd65dae70bcf3ba2d2489/CloudStreamForms/CloudStreamForms/Script/MainChrome.cs#L388
|
||||
realDecoder = when {
|
||||
|
@ -93,33 +141,21 @@ class CustomDecoder : SubtitleDecoder {
|
|||
TAG,
|
||||
"Decoder selected: $realDecoder"
|
||||
)
|
||||
val decoder = realDecoder
|
||||
if (decoder != null) {
|
||||
realDecoder?.let { decoder ->
|
||||
decoder.dequeueInputBuffer()?.let { buff ->
|
||||
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 ->
|
||||
fullStr = fullStr.replace(rgx, "\n")
|
||||
}
|
||||
fullStr.replace(Regex("(\r\n|\r|\n){2,}"), "\n")
|
||||
|
||||
buff.data = ByteBuffer.wrap(fullStr.toByteArray())
|
||||
} catch (e: Exception) {
|
||||
data.position(pos)
|
||||
buff.data = data
|
||||
captionRegex.forEach { rgx ->
|
||||
str = str.replace(rgx, "\n")
|
||||
}
|
||||
} else {
|
||||
buff.data = data
|
||||
}
|
||||
|
||||
buff.data = ByteBuffer.wrap(str.toByteArray())
|
||||
|
||||
decoder.queueInputBuffer(buff)
|
||||
Log.i(
|
||||
TAG,
|
||||
"Decoder queueInputBuffer successfully"
|
||||
)
|
||||
}
|
||||
CS3IPlayer.requestSubtitleUpdate?.invoke()
|
||||
}
|
||||
|
|
|
@ -477,15 +477,24 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
): SubtitleData? {
|
||||
val langCode = preferredAutoSelectSubtitles ?: return null
|
||||
val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null
|
||||
|
||||
if (settings)
|
||||
subtitles.firstOrNull { sub ->
|
||||
val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim()
|
||||
t == lang || t.startsWith("$lang ")
|
||||
|| t == langCode
|
||||
}?.let { sub ->
|
||||
return sub
|
||||
if (downloads) {
|
||||
return subtitles.firstOrNull { sub ->
|
||||
(sub.origin == SubtitleOrigin.DOWNLOADED_FILE && sub.name == context?.getString(
|
||||
R.string.default_subtitles
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
sortSubs(subtitles).firstOrNull { sub ->
|
||||
val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim()
|
||||
(settings || (downloads && sub.origin == SubtitleOrigin.DOWNLOADED_FILE)) && t == lang || t.startsWith(
|
||||
"$lang "
|
||||
) || t == langCode
|
||||
}?.let { sub ->
|
||||
return sub
|
||||
}
|
||||
|
||||
// post check in case both did not catch anything
|
||||
if (downloads) {
|
||||
return subtitles.firstOrNull { sub ->
|
||||
(sub.origin == SubtitleOrigin.DOWNLOADED_FILE || sub.name == context?.getString(
|
||||
|
@ -493,10 +502,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun autoSelectFromSettings() {
|
||||
private fun autoSelectFromSettings(): Boolean {
|
||||
// auto select subtitle based of settings
|
||||
val langCode = preferredAutoSelectSubtitles
|
||||
|
||||
|
@ -506,29 +516,34 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
if (setSubtitles(sub)) {
|
||||
player.reloadPlayer(ctx)
|
||||
player.handleEvent(CSPlayerEvent.Play)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun autoSelectFromDownloads() {
|
||||
private fun autoSelectFromDownloads(): Boolean {
|
||||
if (player.getCurrentPreferredSubtitle() == null) {
|
||||
getAutoSelectSubtitle(currentSubs, settings = false, downloads = true)?.let { sub ->
|
||||
context?.let { ctx ->
|
||||
if (setSubtitles(sub)) {
|
||||
player.reloadPlayer(ctx)
|
||||
player.handleEvent(CSPlayerEvent.Play)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun autoSelectSubtitles() {
|
||||
normalSafeApiCall {
|
||||
autoSelectFromSettings()
|
||||
autoSelectFromDownloads()
|
||||
if (!autoSelectFromSettings()) {
|
||||
autoSelectFromDownloads()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -567,7 +582,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
if (season == null)
|
||||
" - ${ctx.getString(R.string.episode)} $episode"
|
||||
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 {
|
||||
""
|
||||
|
|
|
@ -61,6 +61,7 @@ enum class Qualities(var value: Int) {
|
|||
0 -> "Auto"
|
||||
Unknown.value -> ""
|
||||
P2160.value -> "4K"
|
||||
null -> ""
|
||||
else -> "${qual}p"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
style="@style/CheckLabel"
|
||||
android:id="@android:id/text1"
|
||||
tools:text="hello"
|
||||
app:drawableStartCompat="@drawable/ic_baseline_add_24" />
|
||||
app:drawableStartCompat="@drawable/ic_baseline_add_24"
|
||||
app:drawableTint="?attr/textColor" />
|
Loading…
Reference in a new issue