Skip to content

Commit 9cafb2a

Browse files
committed
Fix the unwanted Pr conflights
1 parent cdd65cb commit 9cafb2a

2 files changed

Lines changed: 73 additions & 235 deletions

File tree

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ lifecycleKtx = "2.9.4"
2929
material = "1.14.0-alpha08"
3030
media3 = "1.8.0"
3131
navigationKtx = "2.9.6"
32-
newpipeextractor = "v0.24.8"
32+
ewpipeextractor = "v0.25.2"
3333
nextlibMedia3 = "1.8.0-0.9.0"
3434
nicehttp = "0.4.16"
3535
overlappingpanels = "0.1.5"
Lines changed: 72 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,38 @@
1-
// Made For cs-kraptor By @trup40, @kraptor123, @ByAyzen
21
package com.lagradost.cloudstream3.extractors
32

4-
import com.fasterxml.jackson.annotation.JsonProperty
53
import com.lagradost.cloudstream3.SubtitleFile
6-
import com.lagradost.cloudstream3.app
4+
import com.lagradost.cloudstream3.newAudioFile
75
import com.lagradost.cloudstream3.newSubtitleFile
8-
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
96
import com.lagradost.cloudstream3.utils.ExtractorApi
107
import com.lagradost.cloudstream3.utils.ExtractorLink
11-
import com.lagradost.cloudstream3.utils.ExtractorLinkType
12-
import com.lagradost.cloudstream3.utils.HlsPlaylistParser
13-
import com.lagradost.cloudstream3.utils.SubtitleHelper
148
import com.lagradost.cloudstream3.utils.newExtractorLink
15-
import okhttp3.MediaType.Companion.toMediaType
16-
import okhttp3.RequestBody.Companion.toRequestBody
17-
import java.net.URLDecoder
9+
import org.schabi.newpipe.extractor.stream.StreamInfo
1810

19-
20-
class YoutubeShortLinkExtractor : YoutubeExtractor() {
11+
class YoutubeShortLinkExtractor(
12+
maxResolution: Int? = null
13+
) : YoutubeExtractor(maxResolution) {
2114
override val mainUrl = "https://youtu.be"
2215
}
2316

24-
class YoutubeMobileExtractor : YoutubeExtractor() {
17+
class YoutubeMobileExtractor(
18+
maxResolution: Int? = null
19+
) : YoutubeExtractor(maxResolution) {
2520
override val mainUrl = "https://m.youtube.com"
2621
}
2722

28-
class YoutubeNoCookieExtractor : YoutubeExtractor() {
23+
class YoutubeNoCookieExtractor(
24+
maxResolution: Int? = null
25+
) : YoutubeExtractor(maxResolution) {
2926
override val mainUrl = "https://www.youtube-nocookie.com"
3027
}
3128

32-
open class YoutubeExtractor : ExtractorApi() {
29+
open class YoutubeExtractor(
30+
private val maxResolution: Int? = null
31+
) : ExtractorApi() {
32+
3333
override val mainUrl = "https://www.youtube.com"
34-
override val requiresReferer = false
3534
override val name = "YouTube"
36-
private val youtubeUrl = "https://www.youtube.com"
37-
38-
companion object {
39-
private const val USER_AGENT =
40-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15"
41-
private val HEADERS = mapOf(
42-
"User-Agent" to USER_AGENT,
43-
"Accept-Language" to "en-US,en;q=0.5"
44-
)
45-
}
46-
47-
48-
private fun extractYtCfg(html: String): String? {
49-
val regex = Regex("""ytcfg\.set\(\s*(\{.*?\})\s*\)\s*;""")
50-
val match = regex.find(html)
51-
return match?.groupValues?.getOrNull(1)
52-
}
53-
54-
data class PageConfig(
55-
@JsonProperty("INNERTUBE_API_KEY")
56-
val apiKey: String,
57-
@JsonProperty("INNERTUBE_CLIENT_VERSION")
58-
val clientVersion: String = "2.20240725.01.00",
59-
@JsonProperty("VISITOR_DATA")
60-
val visitorData: String = ""
61-
)
62-
63-
private suspend fun getPageConfig(videoId: String): PageConfig? =
64-
tryParseJson(extractYtCfg(app.get("$mainUrl/watch?v=$videoId", headers = HEADERS).text))
65-
66-
fun extractYouTubeId(url: String): String {
67-
return when {
68-
url.contains("oembed") && url.contains("url=") -> {
69-
val encodedUrl = url.substringAfter("url=").substringBefore("&")
70-
val decodedUrl = URLDecoder.decode(encodedUrl, "UTF-8")
71-
extractYouTubeId(decodedUrl)
72-
}
73-
74-
url.contains("attribution_link") && url.contains("u=") -> {
75-
val encodedUrl = url.substringAfter("u=").substringBefore("&")
76-
val decodedUrl = URLDecoder.decode(encodedUrl, "UTF-8")
77-
extractYouTubeId(decodedUrl)
78-
}
79-
80-
url.contains("watch?v=") -> url.substringAfter("watch?v=").substringBefore("&")
81-
.substringBefore("#")
82-
83-
url.contains("&v=") -> url.substringAfter("&v=").substringBefore("&")
84-
.substringBefore("#")
85-
86-
url.contains("youtu.be/") -> url.substringAfter("youtu.be/").substringBefore("?")
87-
.substringBefore("#").substringBefore("&")
88-
89-
url.contains("/embed/") -> url.substringAfter("/embed/").substringBefore("?")
90-
.substringBefore("#")
91-
92-
url.contains("/v/") -> url.substringAfter("/v/").substringBefore("?")
93-
.substringBefore("#")
94-
95-
url.contains("/e/") -> url.substringAfter("/e/").substringBefore("?")
96-
.substringBefore("#")
97-
98-
url.contains("/shorts/") -> url.substringAfter("/shorts/").substringBefore("?")
99-
.substringBefore("#")
100-
101-
url.contains("/live/") -> url.substringAfter("/live/").substringBefore("?")
102-
.substringBefore("#")
103-
104-
url.contains("/watch/") -> url.substringAfter("/watch/").substringBefore("?")
105-
.substringBefore("#")
106-
107-
url.contains("watch%3Fv%3D") -> url.substringAfter("watch%3Fv%3D")
108-
.substringBefore("%26").substringBefore("#")
109-
110-
url.contains("v%3D") -> url.substringAfter("v%3D").substringBefore("%26")
111-
.substringBefore("#")
112-
113-
else -> error("No Id Found")
114-
}
115-
}
116-
35+
override val requiresReferer = false
11736

11837
override suspend fun getUrl(
11938
url: String,
@@ -122,162 +41,81 @@ open class YoutubeExtractor : ExtractorApi() {
12241
callback: (ExtractorLink) -> Unit
12342
) {
12443
val videoId = extractYouTubeId(url)
125-
val config = getPageConfig(videoId) ?: return
44+
val watchUrl = "$mainUrl/watch?v=$videoId"
12645

127-
val jsonBody = """
128-
{
129-
"context": {
130-
"client": {
131-
"hl": "en",
132-
"gl": "US",
133-
"clientName": "WEB",
134-
"clientVersion": "${config.clientVersion}",
135-
"visitorData": "${config.visitorData}",
136-
"platform": "DESKTOP",
137-
"userAgent": "$USER_AGENT"
138-
}
139-
},
140-
"videoId": "$videoId",
141-
"playbackContext": {
142-
"contentPlaybackContext": {
143-
"html5Preference": "HTML5_PREF_WANTS"
144-
}
145-
}
146-
}
147-
""".toRequestBody("application/json; charset=utf-8".toMediaType())
148-
149-
val response =
150-
app.post(
151-
"$youtubeUrl/youtubei/v1/player?key=${config.apiKey}",
152-
headers = HEADERS,
153-
requestBody = jsonBody
154-
).parsed<Root>()
155-
156-
val captionTracks = response.captions?.playerCaptionsTracklistRenderer?.captionTracks
46+
val streamInfo = StreamInfo.getInfo(watchUrl)
15747

158-
if (captionTracks != null) {
159-
for (caption in captionTracks) {
160-
subtitleCallback.invoke(
161-
newSubtitleFile(
162-
lang =caption.name.simpleText,
163-
url ="${caption.baseUrl}&fmt=ttml" // The default format is not supported
164-
) { headers = HEADERS })
165-
}
166-
}
167-
168-
val hlsUrl = response.streamingData.hlsManifestUrl
169-
val getHls = app.get(hlsUrl, headers = HEADERS).text
170-
val playlist = HlsPlaylistParser.parse(hlsUrl, getHls) ?: return
48+
processStreams(streamInfo, subtitleCallback, callback)
49+
}
17150

172-
var variantIndex = 0
173-
for (tag in playlist.tags) {
174-
val trimmedTag = tag.trim()
175-
if (!trimmedTag.startsWith("#EXT-X-STREAM-INF")) {
176-
continue
177-
}
178-
val variant = playlist.variants.getOrNull(variantIndex++) ?: continue
51+
private suspend fun processStreams(
52+
info: StreamInfo,
53+
subtitleCallback: (SubtitleFile) -> Unit,
54+
callback: (ExtractorLink) -> Unit
55+
): Boolean {
17956

180-
val audioId = trimmedTag.split(",")
181-
.find { it.trim().startsWith("YT-EXT-AUDIO-CONTENT-ID=") }
182-
?.split("=")
183-
?.get(1)
184-
?.trim('"') ?: ""
57+
val videoStreams = info.videoOnlyStreams
58+
?.filterByResolution(maxResolution)
59+
?: emptyList()
18560

186-
val langString =
187-
SubtitleHelper.fromTagToEnglishLanguageName(
188-
audioId.substringBefore(".")
189-
) ?: SubtitleHelper.fromTagToEnglishLanguageName(
190-
audioId.substringBefore("-")
191-
) ?: audioId
61+
if (videoStreams.isEmpty()) return false
19262

193-
val url = variant.url.toString()
63+
val audioStreams = info.audioStreams.orEmpty()
19464

195-
if (url.isBlank()) {
196-
continue
197-
}
65+
videoStreams.forEach { video ->
19866

199-
callback.invoke(
67+
callback(
20068
newExtractorLink(
201-
source = this.name,
202-
name = "Youtube${if (langString.isNotBlank()) " $langString" else ""}",
203-
url = url,
204-
type = ExtractorLinkType.M3U8
69+
source = name,
70+
name = "YouTube ${normalizeCodec(video.codec)}",
71+
url = video.content
20572
) {
206-
this.referer = "${mainUrl}/"
207-
this.quality = variant.format.height
73+
quality = video.height
74+
audioTracks = audioStreams.map { newAudioFile(it.content) }
20875
}
20976
)
21077
}
211-
}
21278

21379

214-
private data class Root(
215-
// val responseContext: ResponseContext,
216-
// val playabilityStatus: PlayabilityStatus,
217-
@JsonProperty("streamingData")
218-
val streamingData: StreamingData,
219-
// val playbackTracking: PlaybackTracking,
220-
@JsonProperty("captions")
221-
val captions: Captions?,
222-
// val videoDetails: VideoDetails,
223-
// val annotations: List<Annotation>,
224-
// val playerConfig: PlayerConfig,
225-
// val storyboards: Storyboards,
226-
// val microformat: Microformat,
227-
// val cards: Cards,
228-
// val trackingParams: String,
229-
// val endscreen: Endscreen,
230-
// val paidContentOverlay: PaidContentOverlay,
231-
// val adPlacements: List<AdPlacement>,
232-
// val adBreakHeartbeatParams: String,
233-
// val frameworkUpdates: FrameworkUpdates,
234-
)
80+
info.subtitles.forEach { subtitle ->
81+
subtitleCallback(
82+
newSubtitleFile(
83+
lang = subtitle.displayLanguageName
84+
?: subtitle.languageTag
85+
?: "Unknown",
86+
url = subtitle.content
87+
)
88+
)
89+
}
90+
91+
return true
92+
}
23593

236-
private data class StreamingData(
237-
//val expiresInSeconds: String,
238-
//val formats: List<Format>,
239-
//val adaptiveFormats: List<AdaptiveFormat>,
240-
@JsonProperty("hlsManifestUrl")
241-
val hlsManifestUrl: String,
242-
//val serverAbrStreamingUrl: String,
243-
)
94+
// ---------------- HELPERS ----------------
24495

245-
private data class Captions(
246-
@JsonProperty("playerCaptionsTracklistRenderer")
247-
val playerCaptionsTracklistRenderer: PlayerCaptionsTracklistRenderer?,
248-
)
96+
private fun extractYouTubeId(url: String): String {
97+
val regex = Regex(
98+
"(?:youtu\\.be/|youtube(?:-nocookie)?\\.com/(?:.*v=|v/|u/\\w/|embed/|shorts/|live/))([\\w-]{11})"
99+
)
100+
return regex.find(url)?.groupValues?.get(1)
101+
?: throw IllegalArgumentException("Invalid YouTube URL: $url")
102+
}
249103

250-
private data class PlayerCaptionsTracklistRenderer(
251-
@JsonProperty("captionTracks")
252-
val captionTracks: List<CaptionTrack>?,
253-
//val audioTracks: List<AudioTrack>,
254-
//val translationLanguages: List<TranslationLanguage>,
255-
//@JsonProperty("defaultAudioTrackIndex")
256-
//val defaultAudioTrackIndex: Long,
257-
)
104+
private fun List<org.schabi.newpipe.extractor.stream.VideoStream>.filterByResolution(
105+
max: Int?
106+
) = if (max == null) this else filter { it.height <= max }
258107

259-
private data class CaptionTrack(
260-
@JsonProperty("baseUrl")
261-
val baseUrl: String,
262-
@JsonProperty("name")
263-
val name: Name,
264-
//val vssId: String,
265-
//val languageCode: String,
266-
//val kind: String?,
267-
//val isTranslatable: Boolean,
268-
//val trackName: String,
269-
)
108+
private fun normalizeCodec(codec: String?): String {
109+
if (codec.isNullOrBlank()) return ""
270110

271-
private data class Name(
272-
@JsonProperty("simpleText")
273-
val simpleText: String,
274-
)
111+
val c = codec.lowercase()
275112

276-
// data class AudioTrack(
277-
// val captionTrackIndices: List<Long>,
278-
// val defaultCaptionTrackIndex: Long,
279-
// val hasDefaultTrack: Boolean,
280-
// val audioTrackId: String,
281-
// val captionsInitialState: String,
282-
// )
113+
return when {
114+
c.startsWith("av01") -> "AV1"
115+
c.startsWith("vp9") -> "VP9"
116+
c.startsWith("avc1") || c.startsWith("h264") -> "H264"
117+
c.startsWith("hev1") || c.startsWith("hvc1") || c.startsWith("hevc") -> "H265"
118+
else -> codec.substringBefore('.').uppercase()
119+
}
120+
}
283121
}

0 commit comments

Comments
 (0)