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>
This commit is contained in:
Jace 2022-07-11 04:36:28 +08:00 committed by GitHub
parent 0cee63479c
commit a1f31da695
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 203 additions and 52 deletions

View file

@ -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
@ -15,3 +16,5 @@ interface AbstractSubProvider {
throw NotImplementedError()
}
}
interface AbstractSubApi : AbstractSubProvider, AuthAPI

View file

@ -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(

View file

@ -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"

View file

@ -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
@ -84,12 +85,14 @@ 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]))")
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))")
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<String>()
fun cleanResources(results: MutableList<AbstractSubtitleEntities.SubtitleEntity>, name: String, link: String) {
fun cleanResources(
results: MutableList<AbstractSubtitleEntities.SubtitleEntity>,
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,8 +160,10 @@ 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()
@ -191,7 +201,8 @@ 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()
@ -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"))

View file

@ -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<ResultFiles>? = 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(

View file

@ -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<AbstractSubtitleEntities.SubtitleEntity> = 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<String>(dialog.context, R.layout.sort_bottom_single_choice)
object : ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>(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<TextView>(R.id.main_text)
val secondaryTextView = view.findViewById<TextView>(R.id.secondary_text)
val drawableEnd = view.findViewById<ImageView>(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<String>
var currentSubtitles: List<AbstractSubtitleEntities.SubtitleEntity> = emptyList()
var currentSubtitle: AbstractSubtitleEntities.SubtitleEntity? = null
val adapter =
dialog.subtitle_adapter.adapter as? ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>
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<AbstractSubtitleEntities.SubtitleEntity>) {
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 {
""

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="@color/check_selection_color"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/white"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,20c-0.29,0 -0.56,-0.06 -0.76,-0.15 -0.71,-0.37 -1.21,-0.88 -1.71,-2.38 -0.51,-1.56 -1.47,-2.29 -2.39,-3 -0.79,-0.61 -1.61,-1.24 -2.32,-2.53C9.29,10.98 9,9.93 9,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.93 -3.07,-7 -7,-7S7,5.07 7,9c0,1.26 0.38,2.65 1.07,3.9 0.91,1.65 1.98,2.48 2.85,3.15 0.81,0.62 1.39,1.07 1.71,2.05 0.6,1.82 1.37,2.84 2.73,3.55 0.51,0.23 1.07,0.35 1.64,0.35 2.21,0 4,-1.79 4,-4h-2c0,1.1 -0.9,2 -2,2zM7.64,2.64L6.22,1.22C4.23,3.21 3,5.96 3,9s1.23,5.79 3.22,7.78l1.41,-1.41C6.01,13.74 5,11.49 5,9s1.01,-4.74 2.64,-6.36zM11.5,9c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5 -1.12,-2.5 -2.5,-2.5 -2.5,1.12 -2.5,2.5z"/>
</vector>

View file

@ -0,0 +1,69 @@
<!--<CheckedTextView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightSmall"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:textColor="?attr/textColor"
tools:text="Example Text"
android:background="?attr/bitDarkerGrayBackground"
android:checkMark="?android:attr/listChoiceIndicatorSingle"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"/>
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingVertical="10dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/drawable_start"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:paddingHorizontal="10dp"
android:src="@drawable/ic_baseline_check_24_listview"
tools:ignore="ContentDescription" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/main_text"
style="@style/CheckLabel"
android:foreground="@null"
android:minHeight="@null"
app:drawableStartCompat="@null"
tools:text="The Boys - S01E02 Cherry" />
<TextView
android:id="@+id/secondary_text"
style="@style/CheckLabel"
android:foreground="@null"
android:minHeight="@null"
android:padding="0dp"
android:textSize="12sp"
app:drawableStartCompat="@null"
tools:text="English · OpenSubtitles" />
</LinearLayout>
<ImageView
tools:src="@drawable/ic_baseline_hearing_24"
android:id="@+id/drawable_end"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="0"
android:paddingHorizontal="10dp"
tools:ignore="ContentDescription" />
</LinearLayout>

View file

@ -462,8 +462,9 @@
<item name="android:ellipsize">marquee</item>
<item name="android:foreground">?attr/selectableItemBackgroundBorderless</item>
<item name="android:drawablePadding">20dp</item>
<item name="drawableTint">@color/check_selection_color</item>
<item name="drawableStartCompat">@drawable/ic_baseline_check_24</item>
<!-- <item name="drawableTint">@color/check_selection_color</item>-->
<!-- Set color in the drawable instead of tint to allow multiple drawables-->
<item name="drawableStartCompat">@drawable/ic_baseline_check_24_listview</item>
</style>
<style name="BlackButton" parent="NiceButton">