small subtitle support

This commit is contained in:
LagradOst 2021-07-01 22:11:33 +02:00
parent 672dc8c8d1
commit 9fc732c68c
12 changed files with 266 additions and 124 deletions

View File

@ -72,7 +72,7 @@ abstract class MainAPI {
}
// callback is fired once a link is found, will return true if method is executed successfully
open fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
open fun loadLinks(data: String, isCasting: Boolean, subtitleCallback : (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit): Boolean {
return false
}
}
@ -120,6 +120,8 @@ enum class TvType {
ONA,
}
data class SubtitleFile(val lang : String, val url : String)
interface SearchResponse {
val name: String
val url: String // PUBLIC URL FOR OPEN IN APP

View File

@ -86,7 +86,8 @@ class DubbedAnimeProvider : MainAPI() {
title, href, getSlug(href), this.name, TvType.Movie, img, null
)
} else {
AnimeSearchResponse(title,
AnimeSearchResponse(
title,
href,
getSlug(href),
this.name,
@ -96,8 +97,10 @@ class DubbedAnimeProvider : MainAPI() {
null,
EnumSet.of(DubStatus.Dubbed),
null,
null)
})
null
)
}
)
}
return returnValue
}
@ -121,7 +124,8 @@ class DubbedAnimeProvider : MainAPI() {
title, href, getSlug(href), this.name, TvType.Movie, img, null
)
} else {
AnimeSearchResponse(title,
AnimeSearchResponse(
title,
href,
getSlug(href),
this.name,
@ -131,14 +135,21 @@ class DubbedAnimeProvider : MainAPI() {
null,
EnumSet.of(DubStatus.Dubbed),
null,
null)
})
null
)
}
)
}
return returnValue
}
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
override fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val serversHTML = (if (data.startsWith(mainUrl)) { // CLASSIC EPISODE
val slug = getSlug(data)
getAnimeEpisode(slug, false).serversHTML
@ -154,11 +165,15 @@ class DubbedAnimeProvider : MainAPI() {
val find = "src=\"(.*?)\".*?label=\"(.*?)\"".toRegex().find(txt)
if (find != null) {
val quality = find.groupValues[2]
callback.invoke(ExtractorLink(this.name,
this.name + " " + quality + if (quality.endsWith('p')) "" else 'p',
fixUrl(find.groupValues[1]),
this.mainUrl,
getQualityFromName(quality)))
callback.invoke(
ExtractorLink(
this.name,
this.name + " " + quality + if (quality.endsWith('p')) "" else 'p',
fixUrl(find.groupValues[1]),
this.mainUrl,
getQualityFromName(quality)
)
)
}
} catch (e: Exception) {
//IDK
@ -172,7 +187,8 @@ class DubbedAnimeProvider : MainAPI() {
val realSlug = slug.replace("movies/", "")
val episode = getAnimeEpisode(realSlug, true)
val poster = episode.previewImg ?: episode.wideImg
return MovieLoadResponse(episode.title,
return MovieLoadResponse(
episode.title,
realSlug,
this.name,
TvType.Movie,
@ -180,7 +196,8 @@ class DubbedAnimeProvider : MainAPI() {
if (poster == null) null else fixUrl(poster),
episode.year?.toIntOrNull(),
episode.desc,
null)
null
)
} else {
val response = khttp.get("$mainUrl/$slug")
val document = Jsoup.parse(response.text)

View File

@ -167,12 +167,14 @@ class ShiroProvider : MainAPI() {
override fun quickSearch(query: String): ArrayList<SearchResponse> {
val returnValue: ArrayList<SearchResponse> = ArrayList()
val response = khttp.get("https://tapi.shiro.is/anime/auto-complete/${
URLEncoder.encode(
query,
"UTF-8"
)
}?token=$token".replace("+", "%20"))
val response = khttp.get(
"https://tapi.shiro.is/anime/auto-complete/${
URLEncoder.encode(
query,
"UTF-8"
)
}?token=$token".replace("+", "%20")
)
if (response.text == "{\"status\":\"Found\",\"data\":[]}") return returnValue // OR ELSE WILL CAUSE WEIRD ERROR
val mapped = response.let { mapper.readValue<ShiroSearchResponse>(it.text) }
@ -185,12 +187,14 @@ class ShiroProvider : MainAPI() {
override fun search(query: String): ArrayList<SearchResponse>? {
if (!autoLoadToken()) return null
val returnValue: ArrayList<SearchResponse> = ArrayList()
val response = khttp.get("https://tapi.shiro.is/advanced?search=${
URLEncoder.encode(
query,
"UTF-8"
)
}&token=$token".replace("+", "%20"))
val response = khttp.get(
"https://tapi.shiro.is/advanced?search=${
URLEncoder.encode(
query,
"UTF-8"
)
}&token=$token".replace("+", "%20")
)
if (response.text == "{\"status\":\"Found\",\"data\":[]}") return returnValue // OR ELSE WILL CAUSE WEIRD ERROR
val mapped = response.let { mapper.readValue<ShiroFullSearchResponse>(it.text) }
@ -235,7 +239,12 @@ class ShiroProvider : MainAPI() {
)
}
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
override fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
return Vidstream().getUrl(data, isCasting) {
callback.invoke(it)
}

View File

@ -29,17 +29,26 @@ class HDMProvider : MainAPI() {
return returnValue
}
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
override fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
if (data == "") return false
val slug = ".*/(.*?)\\.mp4".toRegex().find(data)?.groupValues?.get(1) ?: return false
val response = khttp.get(data)
val key = "playlist\\.m3u8(.*?)\"".toRegex().find(response.text)?.groupValues?.get(1) ?: return false
callback.invoke(ExtractorLink(this.name,
this.name,
"https://hls.1o.to/vod/$slug/playlist.m3u8$key",
"",
Qualities.HD.value,
true))
callback.invoke(
ExtractorLink(
this.name,
this.name,
"https://hls.1o.to/vod/$slug/playlist.m3u8$key",
"",
Qualities.HD.value,
true
)
)
return true
}
@ -51,9 +60,11 @@ class HDMProvider : MainAPI() {
val descript = document.selectFirst("div.synopsis > p").text()
val year = document.select("div.movieInfoAll > div.row > div.col-md-6")?.get(1)?.selectFirst("> p > a")?.text()
?.toIntOrNull()
val data = "src/player/\\?v=(.*?)\"".toRegex().find(response.text)?.groupValues?.get(1) ?: return null
val data = "src/player/\\?v=(.*?)\"".toRegex().find(response.text)?.groupValues?.get(1) ?: return null
return MovieLoadResponse(title, slug, this.name, TvType.Movie,
"$mainUrl/src/player/?v=$data", poster, year, descript, null)
return MovieLoadResponse(
title, slug, this.name, TvType.Movie,
"$mainUrl/src/player/?v=$data", poster, year, descript, null
)
}
}

View File

@ -5,7 +5,6 @@ import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.extractors.M3u8Manifest
import com.lagradost.cloudstream3.utils.getQualityFromName
import org.jsoup.Jsoup
@ -43,7 +42,7 @@ class LookMovieProvider : MainAPI() {
data class LookMovieTokenSubtitle(
@JsonProperty("language") val language: String,
//@JsonProperty("source") val source: String,
@JsonProperty("source") val source: String?,
//@JsonProperty("source_id") val source_id: String,
//@JsonProperty("kind") val kind: String,
//@JsonProperty("id") val id: String,
@ -77,26 +76,34 @@ class LookMovieProvider : MainAPI() {
if (!movies.isNullOrEmpty()) {
for (m in movies) {
val url = "$mainUrl/movies/view/${m.slug}"
returnValue.add(MovieSearchResponse(m.title,
url,
url,//m.slug,
this.name,
TvType.Movie,
m.poster ?: m.backdrop,
m.year?.toIntOrNull()))
returnValue.add(
MovieSearchResponse(
m.title,
url,
url,//m.slug,
this.name,
TvType.Movie,
m.poster ?: m.backdrop,
m.year?.toIntOrNull()
)
)
}
}
if (!shows.isNullOrEmpty()) {
for (s in shows) {
val url = "$mainUrl/shows/view/${s.slug}"
returnValue.add(MovieSearchResponse(s.title,
url,
url,//s.slug,
this.name,
TvType.TvSeries,
s.poster ?: s.backdrop,
s.year?.toIntOrNull()))
returnValue.add(
MovieSearchResponse(
s.title,
url,
url,//s.slug,
this.name,
TvType.TvSeries,
s.poster ?: s.backdrop,
s.year?.toIntOrNull()
)
)
}
}
@ -119,14 +126,15 @@ class LookMovieProvider : MainAPI() {
val poster = posterHolder.selectFirst("> img")?.attr("data-src")
val year = posterHolder.selectFirst("> p.year")?.text()?.toIntOrNull()
returnValue.add(if (isMovie) {
MovieSearchResponse(
name, href, href, this.name, TvType.Movie, poster, year
)
} else
TvSeriesSearchResponse(
name, href, href, this.name, TvType.TvSeries, poster, year, null
)
returnValue.add(
if (isMovie) {
MovieSearchResponse(
name, href, href, this.name, TvType.Movie, poster, year
)
} else
TvSeriesSearchResponse(
name, href, href, this.name, TvType.TvSeries, poster, year, null
)
)
}
return returnValue
@ -138,15 +146,52 @@ class LookMovieProvider : MainAPI() {
return movieList
}
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
val response = khttp.get(data.replace("\$unixtime", unixTime.toString()))
data class LookMovieLinkLoad(val url: String, val extraUrl: String, val isMovie: Boolean)
private fun addSubtitles(subs: List<LookMovieTokenSubtitle>?, subtitleCallback: (SubtitleFile) -> Unit) {
if (subs == null) return
subs.forEach {
if (it.source != "opensubtitle")
subtitleCallback.invoke(SubtitleFile(it.language, fixUrl(it.file)))
}
}
private fun loadCurrentLinks(url: String, callback: (ExtractorLink) -> Unit) {
val response = khttp.get(url.replace("\$unixtime", unixTime.toString()))
M3u8Manifest.extractLinks(response.text).forEach {
callback.invoke(ExtractorLink(this.name,
"${this.name} - ${it.second}",
fixUrl(it.first),
"",
getQualityFromName(it.second),
true))
callback.invoke(
ExtractorLink(
this.name,
"${this.name} - ${it.second}",
fixUrl(it.first),
"",
getQualityFromName(it.second),
true
)
)
}
}
override fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val localData: LookMovieLinkLoad = mapper.readValue(data)
if (localData.isMovie) {
val tokenResponse = khttp.get(localData.url)
val root = mapper.readValue<LookMovieTokenRoot>(tokenResponse.text)
val accessToken = root.data?.accessToken ?: return false
addSubtitles(root.data.subtitles, subtitleCallback)
loadCurrentLinks(localData.extraUrl.replace("\$accessToken", accessToken), callback)
return true
} else {
loadCurrentLinks(localData.url, callback)
val subResponse = khttp.get(localData.extraUrl)
val subs = mapper.readValue<List<LookMovieTokenSubtitle>>(subResponse.text)
addSubtitles(subs, subtitleCallback)
}
return true
}
@ -159,7 +204,7 @@ class LookMovieProvider : MainAPI() {
val watchHeader = document.selectFirst("div.watch-heading")
val nameHeader = watchHeader.selectFirst("> h1.bd-hd")
val year = nameHeader.selectFirst("> span")?.text()?.toIntOrNull()
val name = nameHeader.ownText()
val title = nameHeader.ownText()
val rating = parseRating(watchHeader.selectFirst("> div.movie-rate > div.rate > p > span").text())
val imgElement = document.selectFirst("div.movie-img > p.movie__poster")
val img = imgElement?.attr("style")
@ -173,22 +218,31 @@ class LookMovieProvider : MainAPI() {
val realUrl =
"$mainUrl/api/v1/security/${if (isMovie) "movie" else "show"}-access?${if (isMovie) "id_movie=$id" else "slug=$realSlug"}&token=1&sk=&step=1"
val tokenResponse = khttp.get(realUrl)
val root = mapper.readValue<LookMovieTokenRoot>(tokenResponse.text)
val accessToken = root.data?.accessToken ?: return null
if (isMovie) {
return MovieLoadResponse(name,
val localData = mapper.writeValueAsString(
LookMovieLinkLoad(
realUrl,
"$mainUrl/manifests/movies/json/$id/\$unixtime/\$accessToken/master.m3u8",
true
)
)
return MovieLoadResponse(
title,
slug,
this.name,
TvType.Movie,
"$mainUrl/manifests/movies/json/$id/\$unixtime/$accessToken/master.m3u8",
localData,
poster,
year,
descript,
null,
rating)
rating
)
} else {
val tokenResponse = khttp.get(realUrl)
val root = mapper.readValue<LookMovieTokenRoot>(tokenResponse.text)
val accessToken = root.data?.accessToken ?: return null
val window =
"window\\[\\'show_storage\\'\\] =((.|\\n)*?\\<)".toRegex().find(response.text)?.groupValues?.get(1)
?: return null
@ -208,13 +262,24 @@ class LookMovieProvider : MainAPI() {
val realJson = "[" + json.substring(0, json.lastIndexOf(',')) + "]"
val episodes = mapper.readValue<List<LookMovieEpisode>>(realJson).map {
TvSeriesEpisode(it.title,
val localData = mapper.writeValueAsString(
LookMovieLinkLoad(
"$mainUrl/manifests/shows/json/$accessToken/\$unixtime/${it.idEpisode}/master.m3u8",
"https://lookmovie.io/api/v1/shows/episode-subtitles/?id_episode=${it.idEpisode}",
false
)
)
TvSeriesEpisode(
it.title,
it.season.toIntOrNull(),
it.episode.toIntOrNull(),
"$mainUrl/manifests/shows/json/$accessToken/\$unixtime/${it.idEpisode}/master.m3u8")
localData
)
}.toList()
return TvSeriesLoadResponse(name,
return TvSeriesLoadResponse(
title,
slug,
this.name,
TvType.TvSeries,
@ -224,7 +289,8 @@ class LookMovieProvider : MainAPI() {
descript,
null,
null,
rating)
rating
)
}
}
}

View File

@ -44,22 +44,30 @@ class MeloMovieProvider : MainAPI() {
val currentUrl = "$mainUrl/movie/${i.id}"
val currentPoster = "$mainUrl/assets/images/poster/${i.imdbId}.jpg"
if (i.type == 2) { // TV-SERIES
returnValue.add(TvSeriesSearchResponse(i.title,
currentUrl,
currentUrl,
this.name,
TvType.TvSeries,
currentPoster,
i.year,
null))
returnValue.add(
TvSeriesSearchResponse(
i.title,
currentUrl,
currentUrl,
this.name,
TvType.TvSeries,
currentPoster,
i.year,
null
)
)
} else if (i.type == 1) { // MOVIE
returnValue.add(MovieSearchResponse(i.title,
currentUrl,
currentUrl,
this.name,
TvType.Movie,
currentUrl,
i.year))
returnValue.add(
MovieSearchResponse(
i.title,
currentUrl,
currentUrl,
this.name,
TvType.Movie,
currentUrl,
i.year
)
)
}
}
return returnValue
@ -67,7 +75,7 @@ class MeloMovieProvider : MainAPI() {
// http not https, the links are not https!
private fun fixUrl(url: String): String {
if(url.isEmpty()) return ""
if (url.isEmpty()) return ""
if (url.startsWith("//")) {
return "http:$url"
@ -93,7 +101,12 @@ class MeloMovieProvider : MainAPI() {
return mapper.writeValueAsString(parsed)
}
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
override fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val links = mapper.readValue<List<MeloMovieLink>>(data)
for (link in links) {
callback.invoke(ExtractorLink(this.name, link.name, link.link, "", getQualityFromName(link.name), false))
@ -120,7 +133,8 @@ class MeloMovieProvider : MainAPI() {
if (type == 1) { // MOVIE
val serialize = document.selectFirst("table.accordion__list")
return MovieLoadResponse(title,
return MovieLoadResponse(
title,
slug,
this.name,
TvType.Movie,
@ -128,7 +142,8 @@ class MeloMovieProvider : MainAPI() {
poster,
year,
plot,
imdbUrl)
imdbUrl
)
} else if (type == 2) {
val episodes = ArrayList<TvSeriesEpisode>()
val seasons = document.select("div.accordion__card")
@ -145,7 +160,8 @@ class MeloMovieProvider : MainAPI() {
}
}
episodes.reverse()
return TvSeriesLoadResponse(title,
return TvSeriesLoadResponse(
title,
slug,
this.name,
TvType.TvSeries,
@ -154,7 +170,8 @@ class MeloMovieProvider : MainAPI() {
year,
plot,
null,
imdbUrl)
imdbUrl
)
}
return null
}

View File

@ -80,7 +80,12 @@ class TrailersToProvider : MainAPI() {
return false
}
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
override fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
if (isCasting) return false
val isMovie = data.contains("/web-sources/")
if (isMovie) {
@ -142,7 +147,8 @@ class TrailersToProvider : MainAPI() {
val epDescript = main.selectFirst("> p")?.text()
TvSeriesEpisode(epName, season, episode, href, epPoster, date, epRating, epDescript)
}
return TvSeriesLoadResponse(title,
return TvSeriesLoadResponse(
title,
slug,
this.name,
TvType.TvSeries,
@ -155,10 +161,12 @@ class TrailersToProvider : MainAPI() {
rating,
tags,
duration,
trailer)
trailer
)
} else {
val data = fixUrl(document.selectFirst("content").attr("data-url") ?: return null)
return MovieLoadResponse(title,
return MovieLoadResponse(
title,
slug,
this.name,
TvType.Movie,
@ -170,7 +178,8 @@ class TrailersToProvider : MainAPI() {
rating,
tags,
duration,
trailer)
trailer
)
}
}
}

View File

@ -128,7 +128,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
epData,
holder,
index,
remoteMediaClient?.mediaInfo?.customData)
remoteMediaClient?.mediaInfo?.customData
)
val startAt = remoteMediaClient?.approximateStreamPosition ?: 0
@ -139,12 +140,17 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
val nextId = remoteMediaClient.mediaQueue.itemIds?.get(currentIdIndex?.plus(1) ?: 0)
if (currentIdIndex == null && nextId != null) {
awaitLinks(remoteMediaClient?.queueInsertAndPlayItem(MediaQueueItem.Builder(
mediaItem)
.build(),
nextId,
startAt,
JSONObject())) {
awaitLinks(
remoteMediaClient?.queueInsertAndPlayItem(
MediaQueueItem.Builder(
mediaItem
)
.build(),
nextId,
startAt,
JSONObject()
)
) {
loadMirror(index + 1)
}
} else {
@ -204,7 +210,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
val links = ArrayList<ExtractorLink>()
val res = safeApiCall {
getApiFromName(meta.apiName).loadLinks(epData.data, true) {
getApiFromName(meta.apiName).loadLinks(epData.data, true, { subtitleFile -> }) {
for (i in links) {
if (i.url == it.url) return@loadLinks
}
@ -225,7 +231,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
epData,
jsonCopy,
0,
done)
done
)
/*fun loadIndex(index: Int) {
println("LOAD INDEX::::: $index")
@ -240,8 +247,12 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
}
}*/
awaitLinks(remoteMediaClient?.queueAppendItem(MediaQueueItem.Builder(mediaInfo).build(),
JSONObject())) {
awaitLinks(
remoteMediaClient?.queueAppendItem(
MediaQueueItem.Builder(mediaInfo).build(),
JSONObject()
)
) {
println("FAILED TO LOAD NEXT ITEM")
// loadIndex(1)
}

View File

@ -147,11 +147,11 @@ class EpisodeAdapter(
clickCallback.invoke(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
} else {
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
}
} else {
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
}
}
}

View File

@ -206,7 +206,7 @@ class ResultViewModel : ViewModel() {
}
val links = ArrayList<ExtractorLink>()
val localData = safeApiCall {
getApiFromName(_apiName.value).loadLinks(data, isCasting) {
getApiFromName(_apiName.value).loadLinks(data, isCasting, { subtitleFile -> }) {
for (i in links) {
if (i.url == it.url) return@loadLinks
}

View File

@ -148,7 +148,7 @@ class SearchFragment : Fragment() {
allApi.providersActive = requireActivity().getApiSettings()
//searchViewModel.search("iron man")
(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro")
//(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro")
/*
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim,

View File

@ -7,7 +7,7 @@ object M3u8Manifest {
val data: ArrayList<Pair<String, String>> = ArrayList()
"\"(.*?)\":\"(.*?)\"".toRegex().findAll(m3u8Data).forEach {
var quality = it.groupValues[1].replace("auto", "Auto")
if (quality != "Auto") quality += "p"
if (quality != "Auto" && !quality.endsWith('p')) quality += "p"
val url = it.groupValues[2]
data.add(Pair(url, quality))
}