From 4701def2beadf08263a375ea0cb0013b9872a4fd Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Mon, 30 May 2022 14:13:11 +0200 Subject: [PATCH] subtitle auto encoding and fixed auto subtitles --- app/build.gradle | 3 + .../ui/player/AbstractPlayerFragment.kt | 1 + .../ui/player/CustomSubtitleDecoderFactory.kt | 98 +++++++++++++------ .../cloudstream3/ui/player/GeneratorPlayer.kt | 45 ++++++--- .../cloudstream3/utils/ExtractorApi.kt | 1 + .../layout/sort_bottom_footer_add_choice.xml | 3 +- 6 files changed, 106 insertions(+), 45 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 343b6ecd..17bc00ec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 1e28fde6..339553b5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -394,6 +394,7 @@ abstract class AbstractPlayerFragment( override fun onDestroy() { playerEventListener = null keyEventListener = null + canEnterPipMode = false SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged keepScreenOn(false) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt index fdacf067..482f6b11 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt @@ -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() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 5a3d3f7e..aa8b165c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -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 { "" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 9bbd3fd8..f12a3d91 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -61,6 +61,7 @@ enum class Qualities(var value: Int) { 0 -> "Auto" Unknown.value -> "" P2160.value -> "4K" + null -> "" else -> "${qual}p" } } diff --git a/app/src/main/res/layout/sort_bottom_footer_add_choice.xml b/app/src/main/res/layout/sort_bottom_footer_add_choice.xml index a2c7ed0f..82937ba1 100644 --- a/app/src/main/res/layout/sort_bottom_footer_add_choice.xml +++ b/app/src/main/res/layout/sort_bottom_footer_add_choice.xml @@ -4,4 +4,5 @@ style="@style/CheckLabel" android:id="@android:id/text1" tools:text="hello" - app:drawableStartCompat="@drawable/ic_baseline_add_24" /> \ No newline at end of file + app:drawableStartCompat="@drawable/ic_baseline_add_24" + app:drawableTint="?attr/textColor" /> \ No newline at end of file