Changes (#1513)
This commit is contained in:
@ -9,7 +9,7 @@ ext {
|
|||||||
pkgNameSuffix = 'en.kickassanime'
|
pkgNameSuffix = 'en.kickassanime'
|
||||||
extClass = '.KickAssAnime'
|
extClass = '.KickAssAnime'
|
||||||
libVersion = '13'
|
libVersion = '13'
|
||||||
extVersionCode = 23
|
extVersionCode = 24
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -8,6 +8,7 @@ import androidx.preference.PreferenceScreen
|
|||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.AnimeInfoDto
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.AnimeInfoDto
|
||||||
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.EpisodeResponseDto
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.EpisodeResponseDto
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.LanguagesDto
|
||||||
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.PopularItemDto
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.PopularItemDto
|
||||||
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.PopularResponseDto
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.PopularResponseDto
|
||||||
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.RecentsResponseDto
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.RecentsResponseDto
|
||||||
@ -79,22 +80,28 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
// ============================== Episodes ==============================
|
||||||
private fun episodeListRequest(anime: SAnime, page: Int) =
|
private fun episodeListRequest(anime: SAnime, page: Int, lang: String) =
|
||||||
GET("$API_URL/${anime.url}/episodes?page=$page&lang=ja-JP")
|
GET("$API_URL${anime.url}/episodes?page=$page&lang=$lang")
|
||||||
|
|
||||||
private fun getEpisodeResponse(anime: SAnime, page: Int): EpisodeResponseDto {
|
private fun getEpisodeResponse(anime: SAnime, page: Int, lang: String): EpisodeResponseDto {
|
||||||
return client.newCall(episodeListRequest(anime, page))
|
return client.newCall(episodeListRequest(anime, page, lang))
|
||||||
.execute()
|
.execute()
|
||||||
.parseAs<EpisodeResponseDto>()
|
.parseAs<EpisodeResponseDto>()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||||
val first = getEpisodeResponse(anime, 1)
|
val languages = client.newCall(
|
||||||
|
GET("$API_URL${anime.url}/language"),
|
||||||
|
).execute().parseAs<LanguagesDto>()
|
||||||
|
val prefLang = preferences.getString(PREF_AUDIO_LANG_KEY, PREF_AUDIO_LANG_DEFAULT)!!
|
||||||
|
val lang = languages.result.firstOrNull { it == prefLang } ?: PREF_AUDIO_LANG_DEFAULT
|
||||||
|
|
||||||
|
val first = getEpisodeResponse(anime, 1, lang)
|
||||||
val items = buildList {
|
val items = buildList {
|
||||||
addAll(first.result)
|
addAll(first.result)
|
||||||
|
|
||||||
first.pages.drop(1).forEachIndexed { index, _ ->
|
first.pages.drop(1).forEachIndexed { index, _ ->
|
||||||
addAll(getEpisodeResponse(anime, index + 2).result)
|
addAll(getEpisodeResponse(anime, index + 2, lang).result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +110,7 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
name = "Ep. ${it.episode_string} - ${it.title}"
|
name = "Ep. ${it.episode_string} - ${it.title}"
|
||||||
url = "${anime.url}/ep-${it.episode_string}-${it.slug}"
|
url = "${anime.url}/ep-${it.episode_string}-${it.slug}"
|
||||||
episode_number = it.episode_string.toFloatOrNull() ?: 0F
|
episode_number = it.episode_string.toFloatOrNull() ?: 0F
|
||||||
|
scanlator = lang.getLocale()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,9 +139,12 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
// tested with extensions-lib:9d3dcb0
|
// tested with extensions-lib:9d3dcb0
|
||||||
// override fun getAnimeUrl(anime: SAnime) = "$baseUrl${anime.url}"
|
// override fun getAnimeUrl(anime: SAnime) = "$baseUrl${anime.url}"
|
||||||
|
|
||||||
override fun animeDetailsRequest(anime: SAnime) = GET("$API_URL/${anime.url}")
|
override fun animeDetailsRequest(anime: SAnime) = GET("$API_URL${anime.url}")
|
||||||
|
|
||||||
override fun animeDetailsParse(response: Response): SAnime {
|
override fun animeDetailsParse(response: Response): SAnime {
|
||||||
|
val languages = client.newCall(
|
||||||
|
GET("${response.request.url}/language"),
|
||||||
|
).execute().parseAs<LanguagesDto>()
|
||||||
val anime = response.parseAs<AnimeInfoDto>()
|
val anime = response.parseAs<AnimeInfoDto>()
|
||||||
return SAnime.create().apply {
|
return SAnime.create().apply {
|
||||||
val useEnglish = preferences.getBoolean(PREF_USE_ENGLISH_KEY, PREF_USE_ENGLISH_DEFAULT)
|
val useEnglish = preferences.getBoolean(PREF_USE_ENGLISH_KEY, PREF_USE_ENGLISH_DEFAULT)
|
||||||
@ -147,6 +158,7 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
status = anime.status.parseStatus()
|
status = anime.status.parseStatus()
|
||||||
description = buildString {
|
description = buildString {
|
||||||
append(anime.synopsis + "\n\n")
|
append(anime.synopsis + "\n\n")
|
||||||
|
append("Available Dub Languages: ${languages.result.joinToString(", ") { t -> t.getLocale() }}\n")
|
||||||
append("Season: ${anime.season.capitalize()}\n")
|
append("Season: ${anime.season.capitalize()}\n")
|
||||||
append("Year: ${anime.year}")
|
append("Year: ${anime.year}")
|
||||||
}
|
}
|
||||||
@ -158,12 +170,20 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
override fun searchAnimeParse(response: Response) = throw Exception("Not used")
|
override fun searchAnimeParse(response: Response) = throw Exception("Not used")
|
||||||
|
|
||||||
private fun searchAnimeParse(response: Response, page: Int): AnimesPage {
|
private fun searchAnimeParse(response: Response, page: Int): AnimesPage {
|
||||||
val data = response.parseAs<SearchResponseDto>()
|
val path = response.request.url.encodedPath
|
||||||
val animes = data.result.map(::popularAnimeFromObject)
|
return if (path.endsWith("api/fsearch") || path.endsWith("/anime")) {
|
||||||
return AnimesPage(animes, page < data.maxPage)
|
val data = response.parseAs<SearchResponseDto>()
|
||||||
|
val animes = data.result.map(::popularAnimeFromObject)
|
||||||
|
AnimesPage(animes, page < data.maxPage)
|
||||||
|
} else if (path.endsWith("/recent")) {
|
||||||
|
latestUpdatesParse(response)
|
||||||
|
} else {
|
||||||
|
popularAnimeParse(response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchAnimeRequest(page: Int, query: String, filters: KickAssAnimeFilters.FilterSearchParams): Request {
|
private fun searchAnimeRequest(page: Int, query: String, filters: KickAssAnimeFilters.FilterSearchParams): Request {
|
||||||
|
if (filters.subPage.isNotBlank()) return GET("$baseUrl/api/${filters.subPage}?page=$page")
|
||||||
if (query.isBlank()) throw Exception("Enter query to search")
|
if (query.isBlank()) throw Exception("Enter query to search")
|
||||||
val data = if (filters.filters == "{}") {
|
val data = if (filters.filters == "{}") {
|
||||||
"""{"page":$page,"query":"$query"}"""
|
"""{"page":$page,"query":"$query"}"""
|
||||||
@ -221,6 +241,21 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val audioLangPref = ListPreference(screen.context).apply {
|
||||||
|
key = PREF_AUDIO_LANG_KEY
|
||||||
|
title = PREF_AUDIO_LANG_TITLE
|
||||||
|
entries = locale.map { it.second }.toTypedArray()
|
||||||
|
entryValues = locale.map { it.first }.toTypedArray()
|
||||||
|
setDefaultValue(PREF_AUDIO_LANG_DEFAULT)
|
||||||
|
summary = "%s"
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val videoQualityPref = ListPreference(screen.context).apply {
|
val videoQualityPref = ListPreference(screen.context).apply {
|
||||||
key = PREF_QUALITY_KEY
|
key = PREF_QUALITY_KEY
|
||||||
title = PREF_QUALITY_TITLE
|
title = PREF_QUALITY_TITLE
|
||||||
@ -235,9 +270,25 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
preferences.edit().putString(key, entry).commit()
|
preferences.edit().putString(key, entry).commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val serverPref = ListPreference(screen.context).apply {
|
||||||
|
key = PREF_SERVER_KEY
|
||||||
|
title = PREF_SERVER_TITLE
|
||||||
|
entries = PREF_SERVER_VALUES
|
||||||
|
entryValues = PREF_SERVER_VALUES
|
||||||
|
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||||
|
summary = "%s"
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
screen.addPreference(videoQualityPref)
|
screen.addPreference(videoQualityPref)
|
||||||
|
screen.addPreference(audioLangPref)
|
||||||
screen.addPreference(titlePref)
|
screen.addPreference(titlePref)
|
||||||
|
screen.addPreference(serverPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================= Utilities ==============================
|
// ============================= Utilities ==============================
|
||||||
@ -245,6 +296,10 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
return body.string().let(json::decodeFromString)
|
return body.string().let(json::decodeFromString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.getLocale(): String {
|
||||||
|
return locale.firstOrNull { it.first == this }?.second ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
private fun String.parseStatus() = when (this) {
|
private fun String.parseStatus() = when (this) {
|
||||||
"finished_airing" -> SAnime.COMPLETED
|
"finished_airing" -> SAnime.COMPLETED
|
||||||
"currently_airing" -> SAnime.ONGOING
|
"currently_airing" -> SAnime.ONGOING
|
||||||
@ -253,8 +308,12 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
override fun List<Video>.sort(): List<Video> {
|
||||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||||
|
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
|
||||||
return sortedWith(
|
return sortedWith(
|
||||||
compareBy { it.quality.contains(quality) },
|
compareBy(
|
||||||
|
{ it.quality.contains(quality) },
|
||||||
|
{ it.quality.contains(server, true) },
|
||||||
|
),
|
||||||
).reversed()
|
).reversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,5 +329,21 @@ class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
private const val PREF_QUALITY_TITLE = "Preferred quality"
|
private const val PREF_QUALITY_TITLE = "Preferred quality"
|
||||||
private const val PREF_QUALITY_DEFAULT = "720p"
|
private const val PREF_QUALITY_DEFAULT = "720p"
|
||||||
private val PREF_QUALITY_VALUES = arrayOf("240p", "360p", "480p", "720p", "1080p")
|
private val PREF_QUALITY_VALUES = arrayOf("240p", "360p", "480p", "720p", "1080p")
|
||||||
|
|
||||||
|
private const val PREF_AUDIO_LANG_KEY = "preferred_audio_lang"
|
||||||
|
private const val PREF_AUDIO_LANG_TITLE = "Preferred audio language"
|
||||||
|
private const val PREF_AUDIO_LANG_DEFAULT = "ja-JP"
|
||||||
|
|
||||||
|
// Add new locales to the bottom so it doesn't mess with pref indexes
|
||||||
|
private val locale = arrayOf(
|
||||||
|
Pair("en-US", "English"),
|
||||||
|
Pair("es-ES", "Spanish (España)"),
|
||||||
|
Pair("ja-JP", "Japanese"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val PREF_SERVER_KEY = "preferred_server"
|
||||||
|
private const val PREF_SERVER_TITLE = "Preferred server"
|
||||||
|
private const val PREF_SERVER_DEFAULT = "SapphireDuck"
|
||||||
|
private val PREF_SERVER_VALUES = arrayOf("SapphireDuck", "PinkBird")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.en.kickassanime
|
package eu.kanade.tachiyomi.animeextension.en.kickassanime
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.KickAssAnimeFilters.asQueryPart
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
|
||||||
@ -31,16 +32,21 @@ object KickAssAnimeFilters {
|
|||||||
class YearFilter : QueryPartFilter("Year", KickAssAnimeFiltersData.year)
|
class YearFilter : QueryPartFilter("Year", KickAssAnimeFiltersData.year)
|
||||||
class StatusFilter : QueryPartFilter("Status", KickAssAnimeFiltersData.status)
|
class StatusFilter : QueryPartFilter("Status", KickAssAnimeFiltersData.status)
|
||||||
class TypeFilter : QueryPartFilter("Type", KickAssAnimeFiltersData.type)
|
class TypeFilter : QueryPartFilter("Type", KickAssAnimeFiltersData.type)
|
||||||
|
class SubPageFilter : QueryPartFilter("Sub-page", KickAssAnimeFiltersData.subpage)
|
||||||
|
|
||||||
val filterList = AnimeFilterList(
|
val filterList = AnimeFilterList(
|
||||||
GenreFilter(),
|
GenreFilter(),
|
||||||
YearFilter(),
|
YearFilter(),
|
||||||
StatusFilter(),
|
StatusFilter(),
|
||||||
TypeFilter(),
|
TypeFilter(),
|
||||||
|
AnimeFilter.Separator(),
|
||||||
|
AnimeFilter.Header("NOTE: Overrides & ignores search and other filters"),
|
||||||
|
SubPageFilter(),
|
||||||
)
|
)
|
||||||
|
|
||||||
data class FilterSearchParams(
|
data class FilterSearchParams(
|
||||||
val filters: String = "",
|
val filters: String = "",
|
||||||
|
val subPage: String = "",
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getJsonList(listString: String, name: String): String {
|
private fun getJsonList(listString: String, name: String): String {
|
||||||
@ -66,7 +72,7 @@ object KickAssAnimeFilters {
|
|||||||
val status = filters.asQueryPart<StatusFilter>()
|
val status = filters.asQueryPart<StatusFilter>()
|
||||||
val type = filters.asQueryPart<TypeFilter>()
|
val type = filters.asQueryPart<TypeFilter>()
|
||||||
|
|
||||||
val filters = "{${
|
val filtersQuery = "{${
|
||||||
listOf(
|
listOf(
|
||||||
getJsonList(genre, "genres"),
|
getJsonList(genre, "genres"),
|
||||||
getJsonItem(year, "year"),
|
getJsonItem(year, "year"),
|
||||||
@ -74,7 +80,10 @@ object KickAssAnimeFilters {
|
|||||||
getJsonItem(type, "type"),
|
getJsonItem(type, "type"),
|
||||||
).filter { it.isNotEmpty() }.joinToString(",")
|
).filter { it.isNotEmpty() }.joinToString(",")
|
||||||
}}"
|
}}"
|
||||||
return FilterSearchParams(filters)
|
return FilterSearchParams(
|
||||||
|
filtersQuery,
|
||||||
|
filters.asQueryPart<SubPageFilter>(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private object KickAssAnimeFiltersData {
|
private object KickAssAnimeFiltersData {
|
||||||
@ -279,5 +288,13 @@ object KickAssAnimeFilters {
|
|||||||
Pair("UNKNOWN", "\"unknown\""),
|
Pair("UNKNOWN", "\"unknown\""),
|
||||||
Pair("MUSIC", "\"music\""),
|
Pair("MUSIC", "\"music\""),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val subpage = arrayOf(
|
||||||
|
Pair("<Select>", ""),
|
||||||
|
Pair("Trending", "show/trending"),
|
||||||
|
Pair("Anime", "anime"),
|
||||||
|
Pair("Recently Added", "show/recent"),
|
||||||
|
Pair("Popular Shows", "show/popular"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,3 +75,8 @@ data class VideoDto(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class SubtitlesDto(val name: String, val language: String, val src: String)
|
data class SubtitlesDto(val name: String, val language: String, val src: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LanguagesDto(
|
||||||
|
val result: List<String>,
|
||||||
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors
|
package eu.kanade.tachiyomi.animeextension.en.kickassanime.extractors
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.VideoDto
|
import eu.kanade.tachiyomi.animeextension.en.kickassanime.dto.VideoDto
|
||||||
import eu.kanade.tachiyomi.animesource.model.Track
|
import eu.kanade.tachiyomi.animesource.model.Track
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
@ -9,15 +10,11 @@ import eu.kanade.tachiyomi.network.GET
|
|||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import java.text.CharacterIterator
|
||||||
|
import java.text.StringCharacterIterator
|
||||||
|
|
||||||
class KickAssAnimeExtractor(private val client: OkHttpClient, private val json: Json) {
|
class KickAssAnimeExtractor(private val client: OkHttpClient, private val json: Json) {
|
||||||
private val isStable by lazy {
|
|
||||||
runCatching {
|
|
||||||
Track("", "")
|
|
||||||
false
|
|
||||||
}.getOrDefault(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun videosFromUrl(url: String): List<Video> {
|
fun videosFromUrl(url: String): List<Video> {
|
||||||
val idQuery = url.substringAfterLast("?")
|
val idQuery = url.substringAfterLast("?")
|
||||||
val baseUrl = url.substringBeforeLast("/") // baseUrl + endpoint/player
|
val baseUrl = url.substringBeforeLast("/") // baseUrl + endpoint/player
|
||||||
@ -44,23 +41,19 @@ class KickAssAnimeExtractor(private val client: OkHttpClient, private val json:
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val subtitles = if (isStable || videoObject.subtitles.isEmpty()) {
|
val subtitles = videoObject.subtitles.map {
|
||||||
emptyList()
|
val subUrl: String = it.src.let { src ->
|
||||||
} else {
|
if (src.startsWith("/")) {
|
||||||
videoObject.subtitles.map {
|
baseUrl.substringBeforeLast("/") + "/$src"
|
||||||
val subUrl: String = it.src.let { src ->
|
} else {
|
||||||
if (src.startsWith("/")) {
|
src
|
||||||
baseUrl.substringBeforeLast("/") + "/$src"
|
|
||||||
} else {
|
|
||||||
src
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val language = "${it.name} (${it.language})"
|
|
||||||
|
|
||||||
println("subUrl -> $subUrl")
|
|
||||||
Track(subUrl, language)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val language = "${it.name} (${it.language})"
|
||||||
|
|
||||||
|
println("subUrl -> $subUrl")
|
||||||
|
Track(subUrl, language)
|
||||||
}
|
}
|
||||||
|
|
||||||
val masterPlaylist = client.newCall(GET(videoObject.playlistUrl)).execute()
|
val masterPlaylist = client.newCall(GET(videoObject.playlistUrl)).execute()
|
||||||
@ -85,24 +78,40 @@ class KickAssAnimeExtractor(private val client: OkHttpClient, private val json:
|
|||||||
|
|
||||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||||
|
|
||||||
if (isStable) {
|
Video(videoUrl, "$prefix - $resolution", videoUrl, subtitleTracks = subs)
|
||||||
Video(videoUrl, "$prefix - $resolution", videoUrl)
|
|
||||||
} else {
|
|
||||||
Video(videoUrl, "$prefix - $resolution", videoUrl, subtitleTracks = subs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractVideosFromDash(playlist: String, prefix: String, subs: List<Track>): List<Video> {
|
private fun extractVideosFromDash(playlist: String, prefix: String, subs: List<Track>): List<Video> {
|
||||||
return playlist.split("<Representation").drop(1).dropLast(1).map {
|
// Parsing dash with Jsoup :YEP:
|
||||||
val resolution = it.substringAfter("height=\"").substringBefore('"') + "p"
|
val document = Jsoup.parse(playlist)
|
||||||
val url = it.substringAfter("<BaseURL>").substringBefore("</Base")
|
val audioList = document.select("Representation[mimetype~=audio]").map { audioSrc ->
|
||||||
.replace("&", "&")
|
Track(audioSrc.text(), formatBits(audioSrc.attr("bandwidth").toLongOrNull() ?: 0L) ?: "audio")
|
||||||
if (isStable) {
|
}
|
||||||
Video(url, "$prefix - $resolution", url)
|
return document.select("Representation[mimetype~=video]").map { videoSrc ->
|
||||||
} else {
|
Video(
|
||||||
Video(url, "$prefix - $resolution", url, subtitleTracks = subs)
|
videoSrc.text(),
|
||||||
}
|
"$prefix - ${videoSrc.attr("height")}p - ${formatBits(videoSrc.attr("bandwidth").toLongOrNull() ?: 0L)}",
|
||||||
|
videoSrc.text(),
|
||||||
|
audioTracks = audioList,
|
||||||
|
subtitleTracks = subs,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================= Utilities ==============================
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
fun formatBits(bits: Long): String? {
|
||||||
|
var bits = bits
|
||||||
|
if (-1000 < bits && bits < 1000) {
|
||||||
|
return "${bits}b"
|
||||||
|
}
|
||||||
|
val ci: CharacterIterator = StringCharacterIterator("kMGTPE")
|
||||||
|
while (bits <= -999950 || bits >= 999950) {
|
||||||
|
bits /= 1000
|
||||||
|
ci.next()
|
||||||
|
}
|
||||||
|
return java.lang.String.format("%.2f%cbs", bits / 1000.0, ci.current())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user