forked from recloudstream/cloudstream
Add internal support for subtitle headers + season names
This commit is contained in:
parent
c8cd6f921d
commit
53965b13fb
13 changed files with 71 additions and 30 deletions
|
@ -13,7 +13,8 @@ class AbstractSubtitleEntities {
|
|||
var epNumber: Int? = null,
|
||||
var seasonNumber: Int? = null,
|
||||
var year: Int? = null,
|
||||
var isHearingImpaired: Boolean = false
|
||||
var isHearingImpaired: Boolean = false,
|
||||
var headers: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
data class SubtitleSearch(
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.util.Log
|
|||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.imdbUrlToIdNullable
|
||||
import com.lagradost.cloudstream3.network.CloudflareKiller
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||
|
@ -19,6 +20,8 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
|
||||
override fun logOut() {}
|
||||
|
||||
private val interceptor = CloudflareKiller()
|
||||
|
||||
companion object {
|
||||
const val host = "https://indexsubtitle.com"
|
||||
const val TAG = "INDEXSUBS"
|
||||
|
@ -122,12 +125,13 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
type = if (seasonNum > 0) TvType.TvSeries else TvType.Movie,
|
||||
epNumber = epNum,
|
||||
seasonNumber = seasonNum,
|
||||
year = yearNum
|
||||
year = yearNum,
|
||||
headers = interceptor.getCookieHeaders(link).toMap()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val document = app.get("$host/?search=$queryText").document
|
||||
val document = app.get("$host/?search=$queryText", interceptor = interceptor).document
|
||||
|
||||
document.select("div.my-3.p-3 div.media").map { block ->
|
||||
if (seasonNum > 0) {
|
||||
|
@ -159,7 +163,7 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
val urlItem = fixUrl(
|
||||
it.selectFirst("a")!!.attr("href")
|
||||
)
|
||||
val itemDoc = app.get(urlItem).document
|
||||
val itemDoc = app.get(urlItem, interceptor = interceptor).document
|
||||
val id = imdbUrlToIdNullable(
|
||||
itemDoc.selectFirst("div.d-flex span.badge.badge-primary")?.parent()
|
||||
?.attr("href")
|
||||
|
@ -198,7 +202,7 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
val results = mutableListOf<AbstractSubtitleEntities.SubtitleEntity>()
|
||||
|
||||
urlItems.forEach { url ->
|
||||
val request = app.get(url)
|
||||
val request = app.get(url, interceptor = interceptor)
|
||||
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()
|
||||
|
@ -231,7 +235,7 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
val seasonNum = data.seasonNumber
|
||||
val epNum = data.epNumber
|
||||
|
||||
val req = app.get(data.data)
|
||||
val req = app.get(data.data, interceptor = interceptor)
|
||||
|
||||
if (req.isSuccessful) {
|
||||
val document = req.document
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.google.android.exoplayer2.ui.SubtitleView
|
|||
import com.google.android.exoplayer2.upstream.DataSource
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource
|
||||
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
|
||||
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
|
||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache
|
||||
|
@ -442,7 +443,14 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
var requestSubtitleUpdate: (() -> Unit)? = null
|
||||
|
||||
private fun createOnlineSource(link: ExtractorLink): DataSource.Factory {
|
||||
private fun createOnlineSource(headers: Map<String, String>): HttpDataSource.Factory {
|
||||
val source = OkHttpDataSource.Factory(app.baseClient).setUserAgent(USER_AGENT)
|
||||
return source.apply {
|
||||
setDefaultRequestProperties(headers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createOnlineSource(link: ExtractorLink): HttpDataSource.Factory {
|
||||
val provider = getApiFromNameNull(link.source)
|
||||
val interceptor = provider?.getVideoInterceptor(link)
|
||||
|
||||
|
@ -813,7 +821,8 @@ class CS3IPlayer : IPlayer {
|
|||
// See setPreferredTextLanguage
|
||||
it.language!!,
|
||||
SubtitleOrigin.EMBEDDED_IN_VIDEO,
|
||||
it.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP
|
||||
it.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP,
|
||||
emptyMap()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -981,9 +990,10 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
val mediaItem = getMediaItem(MimeTypes.VIDEO_MP4, data.uri)
|
||||
val offlineSourceFactory = context.createOfflineSource()
|
||||
val onlineSourceFactory = createOnlineSource(emptyMap())
|
||||
|
||||
val (subSources, activeSubtitles) = getSubSources(
|
||||
onlineSourceFactory = offlineSourceFactory,
|
||||
onlineSourceFactory = onlineSourceFactory,
|
||||
offlineSourceFactory = offlineSourceFactory,
|
||||
subtitleHelper,
|
||||
)
|
||||
|
@ -997,7 +1007,7 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
private fun getSubSources(
|
||||
onlineSourceFactory: DataSource.Factory?,
|
||||
onlineSourceFactory: HttpDataSource.Factory?,
|
||||
offlineSourceFactory: DataSource.Factory?,
|
||||
subHelper: PlayerSubtitleHelper,
|
||||
): Pair<List<SingleSampleMediaSource>, List<SubtitleData>> {
|
||||
|
@ -1021,7 +1031,10 @@ class CS3IPlayer : IPlayer {
|
|||
SubtitleOrigin.URL -> {
|
||||
if (onlineSourceFactory != null) {
|
||||
activeSubtitles.add(sub)
|
||||
SingleSampleMediaSource.Factory(onlineSourceFactory)
|
||||
SingleSampleMediaSource.Factory(onlineSourceFactory.apply {
|
||||
if (sub.headers.isNotEmpty())
|
||||
this.setDefaultRequestProperties(sub.headers)
|
||||
})
|
||||
.createMediaSource(subConfig, C.TIME_UNSET)
|
||||
} else {
|
||||
null
|
||||
|
|
|
@ -84,7 +84,8 @@ class DownloadFileGenerator(
|
|||
realName.ifBlank { ctx.getString(R.string.default_subtitles) },
|
||||
file.second.toString(),
|
||||
SubtitleOrigin.DOWNLOADED_FILE,
|
||||
name.toSubtitleMimeType()
|
||||
name.toSubtitleMimeType(),
|
||||
emptyMap()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -399,7 +399,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
name = getName(currentSubtitle, true),
|
||||
url = url,
|
||||
origin = SubtitleOrigin.URL,
|
||||
mimeType = url.toSubtitleMimeType()
|
||||
mimeType = url.toSubtitleMimeType(),
|
||||
headers = currentSubtitle.headers
|
||||
)
|
||||
runOnMainThread {
|
||||
addAndSelectSubtitles(subtitle)
|
||||
|
@ -483,7 +484,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
name,
|
||||
uri.toString(),
|
||||
SubtitleOrigin.DOWNLOADED_FILE,
|
||||
name.toSubtitleMimeType()
|
||||
name.toSubtitleMimeType(),
|
||||
emptyMap()
|
||||
)
|
||||
|
||||
addAndSelectSubtitles(subtitleData)
|
||||
|
|
|
@ -29,12 +29,14 @@ enum class SubtitleOrigin {
|
|||
/**
|
||||
* @param name To be displayed in the player
|
||||
* @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend language
|
||||
* @param headers if empty it will use the base onlineDataSource headers else only the specified headers
|
||||
* */
|
||||
data class SubtitleData(
|
||||
val name: String,
|
||||
val url: String,
|
||||
val origin: SubtitleOrigin,
|
||||
val mimeType: String,
|
||||
val headers: Map<String, String>
|
||||
)
|
||||
|
||||
class PlayerSubtitleHelper {
|
||||
|
@ -71,7 +73,8 @@ class PlayerSubtitleHelper {
|
|||
name = subtitleFile.lang,
|
||||
url = subtitleFile.url,
|
||||
origin = SubtitleOrigin.URL,
|
||||
mimeType = subtitleFile.url.toSubtitleMimeType()
|
||||
mimeType = subtitleFile.url.toSubtitleMimeType(),
|
||||
headers = emptyMap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -409,6 +409,11 @@ class ResultViewModel2 : ViewModel() {
|
|||
private const val EPISODE_RANGE_SIZE = 20
|
||||
private const val EPISODE_RANGE_OVERLOAD = 30
|
||||
|
||||
private fun List<SeasonData>?.getSeason(season: Int?): SeasonData? {
|
||||
if (season == null) return null
|
||||
return this?.firstOrNull { it.season == season }
|
||||
}
|
||||
|
||||
private fun filterName(name: String?): String? {
|
||||
if (name == null) return null
|
||||
Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let {
|
||||
|
@ -1476,11 +1481,18 @@ class ResultViewModel2 : ViewModel() {
|
|||
if (isMovie || currentSeasons.size <= 1) null else
|
||||
when (indexer.season) {
|
||||
0 -> txt(R.string.no_season)
|
||||
else -> txt(
|
||||
R.string.season_format,
|
||||
txt(R.string.season),
|
||||
indexer.season
|
||||
) //TODO FIX DISPLAYNAME
|
||||
else -> {
|
||||
val seasonNames = (currentResponse as? EpisodeResponse)?.seasonNames
|
||||
val seasonData =
|
||||
seasonNames.getSeason(indexer.season)
|
||||
val suffix = seasonData?.name?.let { " $it" } ?: ""
|
||||
txt(
|
||||
R.string.season_format,
|
||||
txt(R.string.season),
|
||||
seasonData?.displaySeason ?: indexer.season,
|
||||
suffix
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -1578,6 +1590,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
val id = mainId + episode + idIndex * 1000000
|
||||
if (!existingEpisodes.contains(episode)) {
|
||||
existingEpisodes.add(id)
|
||||
val seasonData = loadResponse.seasonNames.getSeason(i.season)
|
||||
val eps =
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
|
@ -1585,7 +1598,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
i.posterUrl,
|
||||
episode,
|
||||
null,
|
||||
i.season,
|
||||
seasonData?.displaySeason ?: i.season,
|
||||
i.data,
|
||||
loadResponse.apiName,
|
||||
id,
|
||||
|
@ -1621,7 +1634,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
existingEpisodes.add(id)
|
||||
val seasonIndex = episode.season?.minus(1)
|
||||
val currentSeason =
|
||||
loadResponse.seasonNames?.getOrNull(seasonIndex ?: -1)
|
||||
loadResponse.seasonNames.getSeason(episode.season)
|
||||
|
||||
val ep =
|
||||
buildResultEpisode(
|
||||
|
@ -1630,7 +1643,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
episode.posterUrl,
|
||||
episodeIndex,
|
||||
seasonIndex,
|
||||
currentSeason?.season ?: episode.season,
|
||||
currentSeason?.displaySeason ?: episode.season,
|
||||
episode.data,
|
||||
loadResponse.apiName,
|
||||
id,
|
||||
|
@ -1731,10 +1744,19 @@ class ResultViewModel2 : ViewModel() {
|
|||
_dubSubSelections.postValue(dubSelection.map { txt(it) to it })
|
||||
if (loadResponse is EpisodeResponse) {
|
||||
_seasonSelections.postValue(seasonsSelection.map { seasonNumber ->
|
||||
val seasonData = loadResponse.seasonNames.getSeason(seasonNumber)
|
||||
val fixedSeasonNumber = seasonData?.displaySeason ?: seasonNumber
|
||||
val suffix = seasonData?.name?.let { " $it" } ?: ""
|
||||
|
||||
val name =
|
||||
/*loadResponse.seasonNames?.firstOrNull { it.season == seasonNumber }?.name?.let { seasonData ->
|
||||
txt(seasonData)
|
||||
} ?:*/txt(R.string.season_format, txt(R.string.season), seasonNumber) //TODO FIX
|
||||
} ?:*/txt(
|
||||
R.string.season_format,
|
||||
txt(R.string.season),
|
||||
fixedSeasonNumber,
|
||||
suffix
|
||||
)
|
||||
name to seasonNumber
|
||||
})
|
||||
}
|
||||
|
|
|
@ -210,7 +210,6 @@
|
|||
</string>
|
||||
|
||||
<string name="season">Staffel</string>
|
||||
<string name="season_format">%s %d</string>
|
||||
<string name="no_season">Keine Staffel</string>
|
||||
<string name="episode">Episode</string>
|
||||
<string name="episodes">Episoden</string>
|
||||
|
|
|
@ -228,7 +228,6 @@
|
|||
</string>
|
||||
|
||||
<string name="season">Sezona</string>
|
||||
<string name="season_format">%s %d</string>
|
||||
<string name="no_season">Nema sezone</string>
|
||||
<string name="episode">Epizoda</string>
|
||||
<string name="episodes">Epizode</string>
|
||||
|
|
|
@ -208,7 +208,6 @@
|
|||
<string name="acra_report_toast">Sorry, de applicatie is gecrasht. Er wordt een anoniem bugrapport naar de ontwikkelaars gestuurd </string>
|
||||
|
||||
<string name="season">Seizoen</string>
|
||||
<string name="season_format">%s %d</string>
|
||||
<string name="no_season">Geen seizoen</string>
|
||||
<string name="episode">Aflevering</string>
|
||||
<string name="episodes">afleveringen</string>
|
||||
|
|
|
@ -221,7 +221,6 @@
|
|||
|
||||
|
||||
<string name="season">Mùa</string>
|
||||
<string name="season_format">%s %d</string>
|
||||
<string name="no_season">Không có mùa nào</string>
|
||||
<string name="episode">Tập</string>
|
||||
<string name="episodes">Tập</string>
|
||||
|
|
|
@ -231,7 +231,6 @@
|
|||
</string>
|
||||
|
||||
<string name="season">季</string>
|
||||
<string name="season_format">%s %d</string>
|
||||
<string name="no_season">无季</string>
|
||||
<string name="episode">集</string>
|
||||
<string name="episodes">集</string>
|
||||
|
|
|
@ -290,7 +290,7 @@
|
|||
</string>
|
||||
|
||||
<string name="season">Season</string>
|
||||
<string name="season_format">%s %d</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="no_season">No Season</string>
|
||||
<string name="episode">Episode</string>
|
||||
<string name="episodes">Episodes</string>
|
||||
|
|
Loading…
Reference in a new issue