From a1f31da695503d3344e49f44cd8039d00dada7e1 Mon Sep 17 00:00:00 2001 From: Jace <54625750+Jacekun@users.noreply.github.com> Date: Mon, 11 Jul 2022 04:36:28 +0800 Subject: [PATCH] Add hearing impaired indicator from opensubtitles. (#1268) * Added icon to hearing aid subtitles and moved the language to a smaller text below. Co-authored-by: Blatzar <46196380+Blatzar@users.noreply.github.com> --- .../subtitles/AbstractSubProvider.kt | 5 +- .../subtitles/AbstractSubtitleEntities.kt | 4 +- .../syncproviders/AccountManager.kt | 11 +-- .../providers/IndexSubtitleApi.kt | 48 +++++++---- .../providers/OpenSubtitlesApi.kt | 17 ++-- .../cloudstream3/ui/player/GeneratorPlayer.kt | 86 +++++++++++++++---- .../ic_baseline_check_24_listview.xml | 5 ++ .../res/drawable/ic_baseline_hearing_24.xml | 5 ++ .../sort_bottom_single_choice_double_text.xml | 69 +++++++++++++++ app/src/main/res/values/styles.xml | 5 +- 10 files changed, 203 insertions(+), 52 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_check_24_listview.xml create mode 100644 app/src/main/res/drawable/ic_baseline_hearing_24.xml create mode 100644 app/src/main/res/layout/sort_bottom_single_choice_double_text.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubProvider.kt index 7b96f89a..77a1b0b5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubProvider.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.subtitles import androidx.annotation.WorkerThread import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleEntity import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleSearch +import com.lagradost.cloudstream3.syncproviders.AuthAPI interface AbstractSubProvider { @WorkerThread @@ -14,4 +15,6 @@ interface AbstractSubProvider { suspend fun load(data: SubtitleEntity): String? { throw NotImplementedError() } -} \ No newline at end of file +} + +interface AbstractSubApi : AbstractSubProvider, AuthAPI \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubtitleEntities.kt b/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubtitleEntities.kt index 561cc4f8..e7e5b857 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubtitleEntities.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubtitleEntities.kt @@ -9,9 +9,11 @@ class AbstractSubtitleEntities { var lang: String = "en", var data: String = "", //Id or link, depends on provider how to process var type: TvType = TvType.Movie, //Movie, TV series, etc.. + var source: String, var epNumber: Int? = null, var seasonNumber: Int? = null, - var year: Int? = null + var year: Int? = null, + var isHearingImpaired: Boolean = false ) data class SubtitleSearch( diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt index d8c7c869..fd68f940 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt @@ -3,10 +3,7 @@ package com.lagradost.cloudstream3.syncproviders import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.syncproviders.providers.AniListApi -import com.lagradost.cloudstream3.syncproviders.providers.MALApi -import com.lagradost.cloudstream3.syncproviders.providers.NginxApi -import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi +import com.lagradost.cloudstream3.syncproviders.providers.* import java.util.concurrent.TimeUnit abstract class AccountManager(private val defIndex: Int) : AuthAPI { @@ -14,6 +11,8 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { val malApi = MALApi(0) val aniListApi = AniListApi(0) val openSubtitlesApi = OpenSubtitlesApi(0) + // Removed because of cloudflare +// val indexSubtitlesApi = IndexSubtitleApi() val nginxApi = NginxApi(0) // used to login via app intent @@ -39,7 +38,9 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { val subtitleProviders get() = listOf( - openSubtitlesApi + openSubtitlesApi, + // Removed because of cloudflare +// indexSubtitlesApi ) const val appString = "cloudstreamapp" diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt index edbfc53b..0d62cbe4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt @@ -1,13 +1,14 @@ package com.lagradost.cloudstream3.syncproviders.providers import android.util.Log -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.subtitles.AbstractSubProvider +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.imdbUrlToIdNullable +import com.lagradost.cloudstream3.subtitles.AbstractSubApi import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities -import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.utils.SubtitleHelper -class IndexSubtitleApi : AuthAPI, AbstractSubProvider { +class IndexSubtitleApi : AbstractSubApi { override val name = "IndexSubtitle" override val idPrefix = "indexsubtitle" override val requiresLogin = false @@ -42,7 +43,7 @@ class IndexSubtitleApi : AuthAPI, AbstractSubProvider { } } - private fun getOrdinal(num: Int?) : String? { + private fun getOrdinal(num: Int?): String? { return when (num) { 1 -> "First" 2 -> "Second" @@ -83,13 +84,15 @@ class IndexSubtitleApi : AuthAPI, AbstractSubProvider { } } - private fun isRightEps(text: String, seasonNum: Int?, epNum: Int?) : Boolean { - val FILTER_EPS_REGEX = Regex("(?i)((Chapter\\s?0?${epNum})|((Season)?\\s?0?${seasonNum}?\\s?(Episode)\\s?0?${epNum}[^0-9]))|(?i)((S?0?${seasonNum}?E0?${epNum}[^0-9])|(0?${seasonNum}[a-z]0?${epNum}[^0-9]))") + private fun isRightEps(text: String, seasonNum: Int?, epNum: Int?): Boolean { + val FILTER_EPS_REGEX = + Regex("(?i)((Chapter\\s?0?${epNum})|((Season)?\\s?0?${seasonNum}?\\s?(Episode)\\s?0?${epNum}[^0-9]))|(?i)((S?0?${seasonNum}?E0?${epNum}[^0-9])|(0?${seasonNum}[a-z]0?${epNum}[^0-9]))") return text.contains(FILTER_EPS_REGEX) } - private fun haveEps(text: String) : Boolean { - val HAVE_EPS_REGEX = Regex("(?i)((Chapter\\s?0?\\d)|((Season)?\\s?0?\\d?\\s?(Episode)\\s?0?\\d))|(?i)((S?0?\\d?E0?\\d)|(0?\\d[a-z]0?\\d))") + private fun haveEps(text: String): Boolean { + val HAVE_EPS_REGEX = + Regex("(?i)((Chapter\\s?0?\\d)|((Season)?\\s?0?\\d?\\s?(Episode)\\s?0?\\d))|(?i)((S?0?\\d?E0?\\d)|(0?\\d[a-z]0?\\d))") return text.contains(HAVE_EPS_REGEX) } @@ -104,13 +107,18 @@ class IndexSubtitleApi : AuthAPI, AbstractSubProvider { val urlItems = ArrayList() - fun cleanResources(results: MutableList, name: String, link: String) { + fun cleanResources( + results: MutableList, + name: String, + link: String + ) { results.add( AbstractSubtitleEntities.SubtitleEntity( idPrefix = idPrefix, name = name, lang = queryLang.toString(), data = link, + source = this.name, type = if (seasonNum > 0) TvType.TvSeries else TvType.Movie, epNumber = epNum, seasonNumber = seasonNum, @@ -152,11 +160,13 @@ class IndexSubtitleApi : AuthAPI, AbstractSubProvider { it.selectFirst("a")!!.attr("href") ) val itemDoc = app.get(urlItem).document - val id = imdbUrlToIdNullable(itemDoc.selectFirst("div.d-flex span.badge.badge-primary")?.parent() - ?.attr("href"))?.toLongOrNull() + val id = imdbUrlToIdNullable( + itemDoc.selectFirst("div.d-flex span.badge.badge-primary")?.parent() + ?.attr("href") + )?.toLongOrNull() val year = itemDoc.selectFirst("div.d-flex span.badge.badge-success") - ?.ownText() - ?.trim().toString() + ?.ownText() + ?.trim().toString() Log.i(TAG, "id => $id \nyear => $year||$yearNum") if (imdbId > 0) { if (id == imdbId) { @@ -191,12 +201,13 @@ class IndexSubtitleApi : AuthAPI, AbstractSubProvider { val request = app.get(url) if (request.isSuccessful) { request.document.select("div.my-3.p-3 div.media").map { block -> - if (block.select("span.d-block span[data-original-title=Language]").text().trim() + if (block.select("span.d-block span[data-original-title=Language]").text() + .trim() .contains("$queryLang") ) { var name = block.select("strong.text-primary").text().trim() val link = fixUrl(block.selectFirst("a")!!.attr("href")) - if(seasonNum > 0) { + if (seasonNum > 0) { when { isRightEps(name, seasonNum, epNum) -> { cleanResources(results, name, link) @@ -222,7 +233,7 @@ class IndexSubtitleApi : AuthAPI, AbstractSubProvider { val req = app.get(data.data) - if(req.isSuccessful) { + if (req.isSuccessful) { val document = req.document val link = if (document.select("div.my-3.p-3 div.media").size == 1) { fixUrl( @@ -230,7 +241,8 @@ class IndexSubtitleApi : AuthAPI, AbstractSubProvider { ) } else { document.select("div.my-3.p-3 div.media").mapNotNull { block -> - val name = block.selectFirst("strong.d-block.text-primary")?.text()?.trim().toString() + val name = + block.selectFirst("strong.d-block.text-primary")?.text()?.trim().toString() if (seasonNum!! > 0) { if (isRightEps(name, seasonNum, epNum)) { fixUrl(block.selectFirst("a")!!.attr("href")) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt index ae0c86e4..43fcf852 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt @@ -2,22 +2,19 @@ package com.lagradost.cloudstream3.syncproviders.providers import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.ErrorLoadingException -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.TvType -import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.subtitles.AbstractSubProvider +import com.lagradost.cloudstream3.subtitles.AbstractSubApi import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager import com.lagradost.cloudstream3.utils.AppUtils -class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubProvider { +class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi { override val idPrefix = "opensubtitles" override val name = "OpenSubtitles" override val icon = R.drawable.open_subtitles_icon @@ -190,6 +187,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubProv val resSeasonNum = featureDetails?.seasonNumber ?: query.seasonNumber val year = featureDetails?.year ?: query.year val type = if ((resSeasonNum ?: 0) > 0) TvType.TvSeries else TvType.Movie + val isHearingImpaired = attr.hearing_impaired ?: false //Log.i(TAG, "Result id/name => ${item.id} / $name") item.attributes?.files?.forEach { file -> val resultData = file.fileId?.toString() ?: "" @@ -201,9 +199,11 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubProv lang = lang, data = resultData, type = type, + source = this.name, epNumber = resEpNum, seasonNumber = resSeasonNum, - year = year + year = year, + isHearingImpaired = isHearingImpaired ) ) } @@ -277,7 +277,8 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubProv @JsonProperty("release") var release: String? = null, @JsonProperty("url") var url: String? = null, @JsonProperty("files") var files: List? = listOf(), - @JsonProperty("feature_details") var featDetails: ResultFeatureDetails? = ResultFeatureDetails() + @JsonProperty("feature_details") var featDetails: ResultFeatureDetails? = ResultFeatureDetails(), + @JsonProperty("hearing_impaired") var hearing_impaired: Boolean? = null, ) data class ResultFiles( 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 24a87cde..d2502269 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 @@ -13,6 +13,7 @@ import android.view.ViewGroup import android.widget.* import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider @@ -216,25 +217,80 @@ class GeneratorPlayer : FullScreenPlayer() { dismissCallback: (() -> Unit) ) { val providers = subsProviders + val isSingleProvider = subsProviders.size == 1 val dialog = Dialog(context, R.style.AlertDialogCustomBlack) dialog.setContentView(R.layout.dialog_online_subtitles) + var currentSubtitles: List = emptyList() + var currentSubtitle: AbstractSubtitleEntities.SubtitleEntity? = null + + fun getName(entry: AbstractSubtitleEntities.SubtitleEntity, withLanguage: Boolean): String { + if (entry.lang.isBlank() || !withLanguage) { + return entry.name + } + val language = fromTwoLettersToLanguage(entry.lang.trim()) ?: entry.lang + return "$language ${entry.name}" + } + + val layout = R.layout.sort_bottom_single_choice_double_text val arrayAdapter = - ArrayAdapter(dialog.context, R.layout.sort_bottom_single_choice) + object : ArrayAdapter(dialog.context, layout) { + fun setHearingImpairedIcon( + imageViewEnd: ImageView?, + position: Int + ) { + if (imageViewEnd == null) return + val isHearingImpaired = + currentSubtitles.getOrNull(position)?.isHearingImpaired ?: false + + val drawableEnd = if (isHearingImpaired) { + ContextCompat.getDrawable( + context, + R.drawable.ic_baseline_hearing_24 + )?.apply { + setTint( + ContextCompat.getColor( + context, + R.color.textColor + ) + ) + } + } else null + + imageViewEnd.setImageDrawable(drawableEnd) + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: LayoutInflater.from(context) + .inflate(layout, null) + + val item = getItem(position) + + val mainTextView = view.findViewById(R.id.main_text) + val secondaryTextView = view.findViewById(R.id.secondary_text) + val drawableEnd = view.findViewById(R.id.drawable_end) + + mainTextView?.text = item?.let { getName(it, false) } + + val language = item?.let { fromTwoLettersToLanguage(it.lang.trim()) ?: it.lang } ?: "" + val providerSuffix = if (isSingleProvider || item == null) "" else " · ${item.source}" + secondaryTextView?.text = language + providerSuffix + + setHearingImpairedIcon(drawableEnd, position) + return view + } + } dialog.show() - dialog.cancel_btt.setOnClickListener { dialog.dismissSafe() } dialog.subtitle_adapter.choiceMode = AbsListView.CHOICE_MODE_SINGLE dialog.subtitle_adapter.adapter = arrayAdapter - val adapter = dialog.subtitle_adapter.adapter as? ArrayAdapter - - var currentSubtitles: List = emptyList() - var currentSubtitle: AbstractSubtitleEntities.SubtitleEntity? = null + val adapter = + dialog.subtitle_adapter.adapter as? ArrayAdapter dialog.subtitle_adapter.setOnItemClickListener { _, _, position, _ -> currentSubtitle = currentSubtitles.getOrNull(position) ?: return@setOnItemClickListener @@ -242,19 +298,11 @@ class GeneratorPlayer : FullScreenPlayer() { var currentLanguageTwoLetters: String = getAutoSelectLanguageISO639_1() - fun getName(entry: AbstractSubtitleEntities.SubtitleEntity): String { - return if (entry.lang.isBlank()) { - entry.name - } else { - val language = fromTwoLettersToLanguage(entry.lang.trim()) ?: entry.lang - return "$language ${entry.name}" - } - } fun setSubtitlesList(list: List) { currentSubtitles = list adapter?.clear() - adapter?.addAll(currentSubtitles.map { getName(it) }) + adapter?.addAll(currentSubtitles) } val currentTempMeta = getMetaData() @@ -328,7 +376,7 @@ class GeneratorPlayer : FullScreenPlayer() { ioSafe { val url = api.load(currentSubtitle) ?: return@ioSafe val subtitle = SubtitleData( - name = getName(currentSubtitle), + name = getName(currentSubtitle, true), url = url, origin = SubtitleOrigin.URL, mimeType = url.toSubtitleMimeType() @@ -846,7 +894,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/res/drawable/ic_baseline_check_24_listview.xml b/app/src/main/res/drawable/ic_baseline_check_24_listview.xml new file mode 100644 index 00000000..d96cc087 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_check_24_listview.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_hearing_24.xml b/app/src/main/res/drawable/ic_baseline_hearing_24.xml new file mode 100644 index 00000000..995d874b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_hearing_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/sort_bottom_single_choice_double_text.xml b/app/src/main/res/layout/sort_bottom_single_choice_double_text.xml new file mode 100644 index 00000000..af5a3982 --- /dev/null +++ b/app/src/main/res/layout/sort_bottom_single_choice_double_text.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index dffb1461..c4ee24e3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -462,8 +462,9 @@ marquee ?attr/selectableItemBackgroundBorderless 20dp - @color/check_selection_color - @drawable/ic_baseline_check_24 + + + @drawable/ic_baseline_check_24_listview