forked from recloudstream/cloudstream
		
	trailers.to backup fix for movies
This commit is contained in:
		
							parent
							
								
									dcb97a1f63
								
							
						
					
					
						commit
						06bafa9801
					
				
					 6 changed files with 242 additions and 63 deletions
				
			
		|  | @ -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<MovieSearchResponse> = listOf() | ||||
|         var discoverSeries: List<TvSeriesSearchResponse> = listOf() | ||||
|         var topMovies: List<MovieSearchResponse> = listOf() | ||||
|         var topSeries: List<TvSeriesSearchResponse> = 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<SearchResponse>? { | ||||
|         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() | ||||
|             } | ||||
|  |  | |||
|  | @ -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<Int, Int> = hashMapOf() | ||||
|         private var imdbToIdMovies: HashMap<String, Int> = hashMapOf() | ||||
|         private var tmdbToIdTvSeries: HashMap<Int, Int> = hashMapOf() | ||||
|         private var imdbToIdTvSeries: HashMap<String, Int> = 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<List<TrailersMovie>>( | ||||
|                     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<List<TrailersEpisode>>( | ||||
|                     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<TmdbLink>(data) | ||||
|         val mappedData = parseJson<TmdbLink>(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<List<TrailersSubtitleFile>>(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<List<TrailersSubtitleFile>>(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 | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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 = | ||||
|  |  | |||
|  | @ -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) | ||||
|             } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue