diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt index 4d0e32d8..4ef7ee29 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.uwetrottmann.tmdb2.Tmdb import com.uwetrottmann.tmdb2.entities.* +import retrofit2.awaitResponse import java.util.* /** @@ -15,7 +16,8 @@ data class TmdbLink( @JsonProperty("imdbID") val imdbID: String?, @JsonProperty("tmdbID") val tmdbID: Int?, @JsonProperty("episode") val episode: Int?, - @JsonProperty("season") val season: Int? + @JsonProperty("season") val season: Int?, + @JsonProperty("movieName") val movieName: String? = null, ) open class TmdbProvider : MainAPI() { @@ -144,6 +146,7 @@ open class TmdbProvider : MainAPI() { this.id, null, null, + this.title ?: this.original_title, ).toJson(), getImageUrl(this.poster_path), this.release_date?.let { @@ -173,23 +176,33 @@ open class TmdbProvider : MainAPI() { // it.toSearchResponse() // } ?: listOf() - val discoverMovies = tmdb.discoverMovie().build().execute().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - - val discoverSeries = tmdb.discoverTv().build().execute().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - - // https://en.wikipedia.org/wiki/ISO_3166-1 - val topMovies = - tmdb.moviesService().topRated(1, "en-US", "US").execute().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - - val topSeries = tmdb.tvService().topRated(1, "en-US").execute().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() + var discoverMovies: List = listOf() + var discoverSeries: List = listOf() + var topMovies: List = listOf() + var topSeries: List = listOf() + argamap( + { + discoverMovies = tmdb.discoverMovie().build().awaitResponse().body()?.results?.map { + it.toSearchResponse() + } ?: listOf() + }, { + discoverSeries = tmdb.discoverTv().build().awaitResponse().body()?.results?.map { + it.toSearchResponse() + } ?: listOf() + }, { + // https://en.wikipedia.org/wiki/ISO_3166-1 + topMovies = + tmdb.moviesService().topRated(1, "en-US", "US").awaitResponse() + .body()?.results?.map { + it.toSearchResponse() + } ?: listOf() + }, { + topSeries = + tmdb.tvService().topRated(1, "en-US").awaitResponse().body()?.results?.map { + it.toSearchResponse() + } ?: listOf() + } + ) return HomePageResponse( listOf( @@ -232,19 +245,19 @@ open class TmdbProvider : MainAPI() { return if (useMetaLoadResponse) { return if (isTvSeries) { - val body = tmdb.tvService().tv(id, "en-US").execute().body() + val body = tmdb.tvService().tv(id, "en-US").awaitResponse().body() body?.toLoadResponse() } else { - val body = tmdb.moviesService().summary(id, "en-US").execute().body() + val body = tmdb.moviesService().summary(id, "en-US").awaitResponse().body() body?.toLoadResponse() } } else { loadFromTmdb(id)?.let { return it } if (isTvSeries) { - tmdb.tvService().externalIds(id, "en-US").execute().body()?.imdb_id?.let { + tmdb.tvService().externalIds(id, "en-US").awaitResponse().body()?.imdb_id?.let { val fromImdb = loadFromImdb(it) val result = if (fromImdb == null) { - val details = tmdb.tvService().tv(id, "en-US").execute().body() + val details = tmdb.tvService().tv(id, "en-US").awaitResponse().body() loadFromImdb(it, details?.seasons ?: listOf()) ?: loadFromTmdb(id, details?.seasons ?: listOf()) } else { @@ -254,16 +267,14 @@ open class TmdbProvider : MainAPI() { result } } else { - tmdb.moviesService().externalIds(id, "en-US").execute() + tmdb.moviesService().externalIds(id, "en-US").awaitResponse() .body()?.imdb_id?.let { loadFromImdb(it) } } } - - } override suspend fun search(query: String): List? { - return tmdb.searchService().multi(query, 1, "en-Us", "US", true).execute() + return tmdb.searchService().multi(query, 1, "en-Us", "US", true).awaitResponse() .body()?.results?.mapNotNull { it.movie?.toSearchResponse() ?: it.tvShow?.toSearchResponse() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersTwoProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersTwoProvider.kt index 49506823..54b466ad 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersTwoProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersTwoProvider.kt @@ -1,13 +1,10 @@ package com.lagradost.cloudstream3.movieproviders import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.module.kotlin.readValue -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.TvType -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.mapper +import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.metaproviders.TmdbLink import com.lagradost.cloudstream3.metaproviders.TmdbProvider +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.SubtitleHelper @@ -20,6 +17,105 @@ class TrailersTwoProvider : TmdbProvider() { override val useMetaLoadResponse = true override val instantLinkLoading = true + data class TrailersEpisode( + // val tvShowItemID: Long?, + //val tvShow: String, + //val tvShowIMDB: String?, + //val tvShowTMDB: Long?, + @JsonProperty("ItemID") + val itemID: Int, + //val title: String, + //@JsonProperty("IMDb") + @JsonProperty("IMDb") + val imdb: String?, + //@JsonProperty("TMDb") + @JsonProperty("TMDb") + val tmdb: Int?, + //val releaseDate: String, + //val entryDate: String + ) + + data class TrailersMovie( + @JsonProperty("ItemID") + val itemID: Int, + @JsonProperty("IMDb") + val imdb: String?, + @JsonProperty("TMDb") + val tmdb: Int?, + //@JsonProperty("Title") + //val title: String?, + ) + + /*companion object { + private var tmdbToIdMovies: HashMap = hashMapOf() + private var imdbToIdMovies: HashMap = hashMapOf() + private var tmdbToIdTvSeries: HashMap = hashMapOf() + private var imdbToIdTvSeries: HashMap = hashMapOf() + + private const val startDate = 1900 + private const val endDate = 9999 + + fun getEpisode(tmdb: Int?, imdb: String?): Int? { + var currentId: Int? = null + if (tmdb != null) { + currentId = tmdbToIdTvSeries[tmdb] + } + if (imdb != null && currentId == null) { + currentId = imdbToIdTvSeries[imdb] + } + return currentId + } + + fun getMovie(tmdb: Int?, imdb: String?): Int? { + var currentId: Int? = null + if (tmdb != null) { + currentId = tmdbToIdMovies[tmdb] + } + if (imdb != null && currentId == null) { + currentId = imdbToIdMovies[imdb] + } + return currentId + } + + suspend fun fillData(isMovie: Boolean) { + if (isMovie) { + if (tmdbToIdMovies.isNotEmpty() || imdbToIdMovies.isNotEmpty()) { + return + } + parseJson>( + app.get( + "https://trailers.to/movies?from=$startDate-01-01&to=$endDate", + timeout = 30 + ).text + ).forEach { movie -> + movie.imdb?.let { + imdbToIdTvSeries[it] = movie.itemID + } + movie.tmdb?.let { + tmdbToIdTvSeries[it] = movie.itemID + } + } + } else { + if (tmdbToIdTvSeries.isNotEmpty() || imdbToIdTvSeries.isNotEmpty()) { + return + } + parseJson>( + app.get( + "https://trailers.to/episodes?from=$startDate-01-01&to=$endDate", + timeout = 30 + ).text + ).forEach { episode -> + episode.imdb?.let { + imdbToIdTvSeries[it] = episode.itemID + } + episode.tmdb?.let { + tmdbToIdTvSeries[it] = episode.itemID + } + } + } + } + }*/ + override val supportedTypes = setOf( TvType.Movie, TvType.TvSeries, @@ -34,50 +130,99 @@ class TrailersTwoProvider : TmdbProvider() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ): Boolean { - val mappedData = mapper.readValue(data) + val mappedData = parseJson(data) val (id, site) = if (mappedData.imdbID != null) listOf( mappedData.imdbID, "imdb" ) else listOf(mappedData.tmdbID.toString(), "tmdb") val isMovie = mappedData.episode == null && mappedData.season == null - val subtitleUrl = if (isMovie) { - callback.invoke( - ExtractorLink( - this.name, - this.name, - "https://trailers.to/video/$user/$site/$id", - "https://trailers.to", - Qualities.Unknown.value, - false, - ) + val (videoUrl, subtitleUrl) = if (isMovie) { + val suffix = "$user/$site/$id" + Pair( + "https://trailers.to/video/$suffix", + "https://trailers.to/subtitles/$suffix" ) - "https://trailers.to/subtitles/$user/$site/$id" } else { - callback.invoke( - ExtractorLink( - this.name, - this.name, - "https://trailers.to/video/$user/$site/$id/S${mappedData.season ?: 1}E${mappedData.episode ?: 1}", - "https://trailers.to", - Qualities.Unknown.value, - false, - ) + val suffix = "$user/$site/$id/S${mappedData.season ?: 1}E${mappedData.episode ?: 1}" + Pair( + "https://trailers.to/video/$suffix", + "https://trailers.to/subtitles/$suffix" ) - "https://trailers.to/subtitles/$user/$site/$id/S${mappedData.season ?: 1}E${mappedData.episode ?: 1}" } - val subtitles = - app.get(subtitleUrl).text - val subtitlesMapped = mapper.readValue>(subtitles) - subtitlesMapped.forEach { - subtitleCallback.invoke( - SubtitleFile( - SubtitleHelper.fromTwoLettersToLanguage(it.LanguageCode ?: "en") ?: "English", - "https://trailers.to/subtitles/${it.ContentHash ?: return@forEach}/${it.LanguageCode ?: return@forEach}.vtt" // ${it.MetaInfo?.SubFormat ?: "srt"}" - ) + callback.invoke( + ExtractorLink( + this.name, + this.name, + videoUrl, + "https://trailers.to", + Qualities.Unknown.value, + false, ) - } + ) + + argamap( + { + val subtitles = + app.get(subtitleUrl).text + val subtitlesMapped = parseJson>(subtitles) + subtitlesMapped.forEach { + subtitleCallback.invoke( + SubtitleFile( + SubtitleHelper.fromTwoLettersToLanguage(it.LanguageCode ?: "en") + ?: "English", + "https://trailers.to/subtitles/${it.ContentHash ?: return@forEach}/${it.LanguageCode ?: return@forEach}.vtt" // ${it.MetaInfo?.SubFormat ?: "srt"}" + ) + ) + } + }, { + //https://trailers.to/en/quick-search?q=iron man + val name = mappedData.movieName + if (name != null && isMovie) { + app.get("https://trailers.to/en/quick-search?q=${name}").document.select("a.post-minimal") + .mapNotNull { + it?.attr("href") + }.map { Regex("""/movie/(\d+)/""").find(it)?.groupValues?.getOrNull(1) } + .firstOrNull()?.let { movieId -> + val correctUrl = app.get(videoUrl).url + callback.invoke( + ExtractorLink( + this.name, + "${this.name} Backup", + correctUrl.replace("/$user/0/", "/$user/$movieId/"), + "https://trailers.to", + Qualities.Unknown.value, + false, + ) + ) + } + } + } + ) + + /* + // the problem with this code is that it tages ages and the json file is 50mb or so for movies + fillData(isMovie) + val movieId = if (isMovie) { + getMovie(mappedData.tmdbID, mappedData.imdbID) + } else { + getEpisode(mappedData.tmdbID, mappedData.imdbID) + } ?: return@argamap + val request = app.get(data) + val endUrl = request.url + callback.invoke( + ExtractorLink( + this.name, + "${this.name} Backup", + endUrl.replace("/cloudstream/0/", "/cloudstream/$movieId/"), + "https://trailers.to", + Qualities.Unknown.value, + false, + ) + ) + */ + return true } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/Requests.kt b/app/src/main/java/com/lagradost/cloudstream3/network/Requests.kt index 36ca3d3f..625ca4fd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/Requests.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/Requests.kt @@ -293,6 +293,10 @@ open class Requests { .followRedirects(allowRedirects) .followSslRedirects(allowRedirects) .callTimeout(timeout, TimeUnit.SECONDS) + if (timeout > 0) + client + .connectTimeout(timeout, TimeUnit.SECONDS) + .readTimeout(timeout, TimeUnit.SECONDS) if (interceptor != null) client.addInterceptor(interceptor) val request = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index d3bcf47c..8a563927 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -236,6 +236,14 @@ abstract class AbstractPlayerFragment( } } } + is InvalidFileException -> { + showToast( + activity, + "${ctx.getString(R.string.source_error)}\n${exception.message}", + Toast.LENGTH_SHORT + ) + nextMirror() + } else -> { showToast(activity, exception.message, Toast.LENGTH_SHORT) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 8bf46ba3..44142e19 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -554,6 +554,15 @@ class CS3IPlayer : IPlayer { updatedTime() if (!hasUsedFirstRender) { // this insures that we only call this once per player load Log.i(TAG, "Rendered first frame") + + val invalid = exoPlayer?.duration?.let { duration -> + duration < 20000L + } ?: false + if(invalid) { + playerError?.invoke(InvalidFileException("Too short playback")) + return + } + setPreferredSubtitles(currentSubtitles) hasUsedFirstRender = true val format = exoPlayer?.videoFormat diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt index 8a99f065..45e0123f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt @@ -44,6 +44,8 @@ enum class CSPlayerLoading { //IsDone, } +class InvalidFileException(msg : String) : Exception(msg) + //http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 const val STATE_RESUME_WINDOW = "resumeWindow" const val STATE_RESUME_POSITION = "resumePosition"