mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	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 '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' | ||||||
| } | } | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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 { |                                     captionRegex.forEach { rgx -> | ||||||
|                                         data.position(0) |                                         str = str.replace(rgx, "\n") | ||||||
|                                         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 |  | ||||||
|                                     } |                                     } | ||||||
|                                 } else { |  | ||||||
|                                     buff.data = data |  | ||||||
|                                 } |                                 } | ||||||
|  | 
 | ||||||
|  |                                 buff.data = ByteBuffer.wrap(str.toByteArray()) | ||||||
|  | 
 | ||||||
|                                 decoder.queueInputBuffer(buff) |                                 decoder.queueInputBuffer(buff) | ||||||
|  |                                 Log.i( | ||||||
|  |                                     TAG, | ||||||
|  |                                     "Decoder queueInputBuffer successfully" | ||||||
|  |                                 ) | ||||||
|                             } |                             } | ||||||
|                             CS3IPlayer.requestSubtitleUpdate?.invoke() |                             CS3IPlayer.requestSubtitleUpdate?.invoke() | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
|         if (settings) |             return subtitles.firstOrNull { sub -> | ||||||
|             subtitles.firstOrNull { sub -> |                 (sub.origin == SubtitleOrigin.DOWNLOADED_FILE && sub.name == context?.getString( | ||||||
|                 val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim() |                     R.string.default_subtitles | ||||||
|                 t == lang || t.startsWith("$lang ") |                 )) | ||||||
|                         || t == langCode |  | ||||||
|             }?.let { sub -> |  | ||||||
|                 return sub |  | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         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) { |         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,29 +516,34 @@ 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() | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -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 { | ||||||
|                 "" |                 "" | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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" /> | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue