fix: use url parser instead of regex for extracting track type
This commit is contained in:
parent
6e5b6b76a2
commit
f2c167f2dd
|
@ -43,6 +43,7 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||||
import org.schabi.newpipe.extractor.localization.Localization;
|
import org.schabi.newpipe.extractor.localization.Localization;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream.AudioTrackType;
|
||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||||
import org.schabi.newpipe.extractor.utils.Parser;
|
import org.schabi.newpipe.extractor.utils.Parser;
|
||||||
|
@ -436,7 +437,7 @@ public final class YoutubeParsingHelper {
|
||||||
/**
|
/**
|
||||||
* @param playlistId the playlist id to parse
|
* @param playlistId the playlist id to parse
|
||||||
* @return the {@link PlaylistInfo.PlaylistType} extracted from the playlistId (mix playlist
|
* @return the {@link PlaylistInfo.PlaylistType} extracted from the playlistId (mix playlist
|
||||||
* types included)
|
* types included)
|
||||||
* @throws ParsingException if the playlistId is null or empty, if the playlistId is not a mix,
|
* @throws ParsingException if the playlistId is null or empty, if the playlistId is not a mix,
|
||||||
* if it is a mix but it's not based on a specific stream (this is the
|
* if it is a mix but it's not based on a specific stream (this is the
|
||||||
* case for channel or genre mixes)
|
* case for channel or genre mixes)
|
||||||
|
@ -469,7 +470,7 @@ public final class YoutubeParsingHelper {
|
||||||
// 11 characters then it can't be a video id, hence we are dealing with a different
|
// 11 characters then it can't be a video id, hence we are dealing with a different
|
||||||
// type of mix (e.g. genre mixes handled above, of the form RDGMEM{garbage})
|
// type of mix (e.g. genre mixes handled above, of the form RDGMEM{garbage})
|
||||||
throw new ParsingException("Video id could not be determined from mix id: "
|
throw new ParsingException("Video id could not be determined from mix id: "
|
||||||
+ playlistId);
|
+ playlistId);
|
||||||
}
|
}
|
||||||
return playlistId.substring(2);
|
return playlistId.substring(2);
|
||||||
|
|
||||||
|
@ -482,7 +483,7 @@ public final class YoutubeParsingHelper {
|
||||||
/**
|
/**
|
||||||
* @param playlistId the playlist id to parse
|
* @param playlistId the playlist id to parse
|
||||||
* @return the {@link PlaylistInfo.PlaylistType} extracted from the playlistId (mix playlist
|
* @return the {@link PlaylistInfo.PlaylistType} extracted from the playlistId (mix playlist
|
||||||
* types included)
|
* types included)
|
||||||
* @throws ParsingException if the playlistId is null or empty
|
* @throws ParsingException if the playlistId is null or empty
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -510,7 +511,7 @@ public final class YoutubeParsingHelper {
|
||||||
/**
|
/**
|
||||||
* @param playlistUrl the playlist url to parse
|
* @param playlistUrl the playlist url to parse
|
||||||
* @return the {@link PlaylistInfo.PlaylistType} extracted from the playlistUrl's list param
|
* @return the {@link PlaylistInfo.PlaylistType} extracted from the playlistUrl's list param
|
||||||
* (mix playlist types included)
|
* (mix playlist types included)
|
||||||
* @throws ParsingException if the playlistUrl is malformed, if has no list param or if the list
|
* @throws ParsingException if the playlistUrl is malformed, if has no list param or if the list
|
||||||
* param is empty
|
* param is empty
|
||||||
*/
|
*/
|
||||||
|
@ -540,20 +541,20 @@ public final class YoutubeParsingHelper {
|
||||||
}
|
}
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
final byte[] body = JsonWriter.string()
|
final byte[] body = JsonWriter.string()
|
||||||
.object()
|
.object()
|
||||||
.object("context")
|
.object("context")
|
||||||
.object("client")
|
.object("client")
|
||||||
.value("hl", "en-GB")
|
.value("hl", "en-GB")
|
||||||
.value("gl", "GB")
|
.value("gl", "GB")
|
||||||
.value("clientName", "WEB")
|
.value("clientName", "WEB")
|
||||||
.value("clientVersion", HARDCODED_CLIENT_VERSION)
|
.value("clientVersion", HARDCODED_CLIENT_VERSION)
|
||||||
.end()
|
.end()
|
||||||
.object("user")
|
.object("user")
|
||||||
.value("lockedSafetyMode", false)
|
.value("lockedSafetyMode", false)
|
||||||
.end()
|
.end()
|
||||||
.value("fetchLiveState", true)
|
.value("fetchLiveState", true)
|
||||||
.end()
|
.end()
|
||||||
.end().done().getBytes(StandardCharsets.UTF_8);
|
.end().done().getBytes(StandardCharsets.UTF_8);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
final var headers = getClientHeaders("1", HARDCODED_CLIENT_VERSION);
|
final var headers = getClientHeaders("1", HARDCODED_CLIENT_VERSION);
|
||||||
|
@ -637,14 +638,14 @@ public final class YoutubeParsingHelper {
|
||||||
throw new ParsingException(
|
throw new ParsingException(
|
||||||
// CHECKSTYLE:OFF
|
// CHECKSTYLE:OFF
|
||||||
"Could not extract YouTube WEB InnerTube API key from HTML search results page");
|
"Could not extract YouTube WEB InnerTube API key from HTML search results page");
|
||||||
// CHECKSTYLE:ON
|
// CHECKSTYLE:ON
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientVersion == null) {
|
if (clientVersion == null) {
|
||||||
throw new ParsingException(
|
throw new ParsingException(
|
||||||
// CHECKSTYLE:OFF
|
// CHECKSTYLE:OFF
|
||||||
"Could not extract YouTube WEB InnerTube client version from HTML search results page");
|
"Could not extract YouTube WEB InnerTube client version from HTML search results page");
|
||||||
// CHECKSTYLE:ON
|
// CHECKSTYLE:ON
|
||||||
}
|
}
|
||||||
|
|
||||||
keyAndVersionExtracted = true;
|
keyAndVersionExtracted = true;
|
||||||
|
@ -771,30 +772,30 @@ public final class YoutubeParsingHelper {
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
final byte[] json = JsonWriter.string()
|
final byte[] json = JsonWriter.string()
|
||||||
.object()
|
.object()
|
||||||
.object("context")
|
.object("context")
|
||||||
.object("client")
|
.object("client")
|
||||||
.value("clientName", "WEB_REMIX")
|
.value("clientName", "WEB_REMIX")
|
||||||
.value("clientVersion", HARDCODED_YOUTUBE_MUSIC_KEY[2])
|
.value("clientVersion", HARDCODED_YOUTUBE_MUSIC_KEY[2])
|
||||||
.value("hl", "en-GB")
|
.value("hl", "en-GB")
|
||||||
.value("gl", "GB")
|
.value("gl", "GB")
|
||||||
.array("experimentIds").end()
|
.array("experimentIds").end()
|
||||||
.value("experimentsToken", "")
|
.value("experimentsToken", "")
|
||||||
.object("locationInfo").end()
|
.object("locationInfo").end()
|
||||||
.object("musicAppInfo").end()
|
.object("musicAppInfo").end()
|
||||||
.end()
|
.end()
|
||||||
.object("capabilities").end()
|
.object("capabilities").end()
|
||||||
.object("request")
|
.object("request")
|
||||||
.array("internalExperimentFlags").end()
|
.array("internalExperimentFlags").end()
|
||||||
.object("sessionIndex").end()
|
.object("sessionIndex").end()
|
||||||
.end()
|
.end()
|
||||||
.object("activePlayers").end()
|
.object("activePlayers").end()
|
||||||
.object("user")
|
.object("user")
|
||||||
.value("enableSafetyMode", false)
|
.value("enableSafetyMode", false)
|
||||||
.end()
|
.end()
|
||||||
.end()
|
.end()
|
||||||
.value("input", "")
|
.value("input", "")
|
||||||
.end().done().getBytes(StandardCharsets.UTF_8);
|
.end().done().getBytes(StandardCharsets.UTF_8);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
final var headers = new HashMap<>(getOriginReferrerHeaders(YOUTUBE_MUSIC_URL));
|
final var headers = new HashMap<>(getOriginReferrerHeaders(YOUTUBE_MUSIC_URL));
|
||||||
|
@ -838,7 +839,7 @@ public final class YoutubeParsingHelper {
|
||||||
musicClientName = Parser.matchGroup1(INNERTUBE_CLIENT_NAME_REGEX, html);
|
musicClientName = Parser.matchGroup1(INNERTUBE_CLIENT_NAME_REGEX, html);
|
||||||
}
|
}
|
||||||
|
|
||||||
youtubeMusicKey = new String[] {musicKey, musicClientName, musicClientVersion};
|
youtubeMusicKey = new String[]{musicKey, musicClientName, musicClientVersion};
|
||||||
return youtubeMusicKey;
|
return youtubeMusicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -901,7 +902,7 @@ public final class YoutubeParsingHelper {
|
||||||
if (navigationEndpoint.getObject("watchEndpoint").has("startTimeSeconds")) {
|
if (navigationEndpoint.getObject("watchEndpoint").has("startTimeSeconds")) {
|
||||||
url.append("&t=")
|
url.append("&t=")
|
||||||
.append(navigationEndpoint.getObject("watchEndpoint")
|
.append(navigationEndpoint.getObject("watchEndpoint")
|
||||||
.getInt("startTimeSeconds"));
|
.getInt("startTimeSeconds"));
|
||||||
}
|
}
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
@ -1233,24 +1234,24 @@ public final class YoutubeParsingHelper {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return JsonObject.builder()
|
return JsonObject.builder()
|
||||||
.object("context")
|
.object("context")
|
||||||
.object("client")
|
.object("client")
|
||||||
.value("hl", localization.getLocalizationCode())
|
.value("hl", localization.getLocalizationCode())
|
||||||
.value("gl", contentCountry.getCountryCode())
|
.value("gl", contentCountry.getCountryCode())
|
||||||
.value("clientName", "WEB")
|
.value("clientName", "WEB")
|
||||||
.value("clientVersion", getClientVersion())
|
.value("clientVersion", getClientVersion())
|
||||||
.value("originalUrl", "https://www.youtube.com")
|
.value("originalUrl", "https://www.youtube.com")
|
||||||
.value("platform", "DESKTOP")
|
.value("platform", "DESKTOP")
|
||||||
.end()
|
.end()
|
||||||
.object("request")
|
.object("request")
|
||||||
.array("internalExperimentFlags")
|
.array("internalExperimentFlags")
|
||||||
.end()
|
.end()
|
||||||
.value("useSsl", true)
|
.value("useSsl", true)
|
||||||
.end()
|
.end()
|
||||||
.object("user")
|
.object("user")
|
||||||
// TO DO: provide a way to enable restricted mode with:
|
// TO DO: provide a way to enable restricted mode with:
|
||||||
// .value("enableSafetyMode", boolean)
|
// .value("enableSafetyMode", boolean)
|
||||||
.value("lockedSafetyMode", false)
|
.value("lockedSafetyMode", false)
|
||||||
.end()
|
.end()
|
||||||
.end();
|
.end();
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
@ -1262,32 +1263,32 @@ public final class YoutubeParsingHelper {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return JsonObject.builder()
|
return JsonObject.builder()
|
||||||
.object("context")
|
.object("context")
|
||||||
.object("client")
|
.object("client")
|
||||||
.value("clientName", "ANDROID")
|
.value("clientName", "ANDROID")
|
||||||
.value("clientVersion", ANDROID_YOUTUBE_CLIENT_VERSION)
|
.value("clientVersion", ANDROID_YOUTUBE_CLIENT_VERSION)
|
||||||
.value("platform", "MOBILE")
|
.value("platform", "MOBILE")
|
||||||
.value("osName", "Android")
|
.value("osName", "Android")
|
||||||
.value("osVersion", "12")
|
.value("osVersion", "12")
|
||||||
/*
|
/*
|
||||||
A valid Android SDK version is required to be sure to get a valid player
|
A valid Android SDK version is required to be sure to get a valid player
|
||||||
response
|
response
|
||||||
If this parameter is not provided, the player response may be replaced by
|
If this parameter is not provided, the player response may be replaced by
|
||||||
the one of a 5-minute video saying the message "The following content is
|
the one of a 5-minute video saying the message "The following content is
|
||||||
not available on this app. Watch this content on the latest version on
|
not available on this app. Watch this content on the latest version on
|
||||||
YouTube"
|
YouTube"
|
||||||
See https://github.com/TeamNewPipe/NewPipe/issues/8713
|
See https://github.com/TeamNewPipe/NewPipe/issues/8713
|
||||||
The Android SDK version corresponding to the Android version used in
|
The Android SDK version corresponding to the Android version used in
|
||||||
requests is sent
|
requests is sent
|
||||||
*/
|
*/
|
||||||
.value("androidSdkVersion", 31)
|
.value("androidSdkVersion", 31)
|
||||||
.value("hl", localization.getLocalizationCode())
|
.value("hl", localization.getLocalizationCode())
|
||||||
.value("gl", contentCountry.getCountryCode())
|
.value("gl", contentCountry.getCountryCode())
|
||||||
.end()
|
.end()
|
||||||
.object("user")
|
.object("user")
|
||||||
// TO DO: provide a way to enable restricted mode with:
|
// TO DO: provide a way to enable restricted mode with:
|
||||||
// .value("enableSafetyMode", boolean)
|
// .value("enableSafetyMode", boolean)
|
||||||
.value("lockedSafetyMode", false)
|
.value("lockedSafetyMode", false)
|
||||||
.end()
|
.end()
|
||||||
.end();
|
.end();
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
@ -1299,27 +1300,27 @@ public final class YoutubeParsingHelper {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return JsonObject.builder()
|
return JsonObject.builder()
|
||||||
.object("context")
|
.object("context")
|
||||||
.object("client")
|
.object("client")
|
||||||
.value("clientName", "IOS")
|
.value("clientName", "IOS")
|
||||||
.value("clientVersion", IOS_YOUTUBE_CLIENT_VERSION)
|
.value("clientVersion", IOS_YOUTUBE_CLIENT_VERSION)
|
||||||
.value("deviceMake", "Apple")
|
.value("deviceMake", "Apple")
|
||||||
// Device model is required to get 60fps streams
|
// Device model is required to get 60fps streams
|
||||||
.value("deviceModel", IOS_DEVICE_MODEL)
|
.value("deviceModel", IOS_DEVICE_MODEL)
|
||||||
.value("platform", "MOBILE")
|
.value("platform", "MOBILE")
|
||||||
.value("osName", "iOS")
|
.value("osName", "iOS")
|
||||||
// The value of this field seems to use the following structure:
|
// The value of this field seems to use the following structure:
|
||||||
// "iOS version.0.build version"
|
// "iOS version.0.build version"
|
||||||
// The build version corresponding to the iOS version used can be found on
|
// The build version corresponding to the iOS version used can be found on
|
||||||
// https://www.theiphonewiki.com/wiki/Firmware/iPhone/15.x#iPhone_13
|
// https://www.theiphonewiki.com/wiki/Firmware/iPhone/15.x#iPhone_13
|
||||||
.value("osVersion", "15.6.0.19G71")
|
.value("osVersion", "15.6.0.19G71")
|
||||||
.value("hl", localization.getLocalizationCode())
|
.value("hl", localization.getLocalizationCode())
|
||||||
.value("gl", contentCountry.getCountryCode())
|
.value("gl", contentCountry.getCountryCode())
|
||||||
.end()
|
.end()
|
||||||
.object("user")
|
.object("user")
|
||||||
// TO DO: provide a way to enable restricted mode with:
|
// TO DO: provide a way to enable restricted mode with:
|
||||||
// .value("enableSafetyMode", boolean)
|
// .value("enableSafetyMode", boolean)
|
||||||
.value("lockedSafetyMode", false)
|
.value("lockedSafetyMode", false)
|
||||||
.end()
|
.end()
|
||||||
.end();
|
.end();
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
@ -1332,22 +1333,22 @@ public final class YoutubeParsingHelper {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return JsonObject.builder()
|
return JsonObject.builder()
|
||||||
.object("context")
|
.object("context")
|
||||||
.object("client")
|
.object("client")
|
||||||
.value("clientName", "TVHTML5_SIMPLY_EMBEDDED_PLAYER")
|
.value("clientName", "TVHTML5_SIMPLY_EMBEDDED_PLAYER")
|
||||||
.value("clientVersion", TVHTML5_SIMPLY_EMBED_CLIENT_VERSION)
|
.value("clientVersion", TVHTML5_SIMPLY_EMBED_CLIENT_VERSION)
|
||||||
.value("clientScreen", "EMBED")
|
.value("clientScreen", "EMBED")
|
||||||
.value("platform", "TV")
|
.value("platform", "TV")
|
||||||
.value("hl", localization.getLocalizationCode())
|
.value("hl", localization.getLocalizationCode())
|
||||||
.value("gl", contentCountry.getCountryCode())
|
.value("gl", contentCountry.getCountryCode())
|
||||||
.end()
|
.end()
|
||||||
.object("thirdParty")
|
.object("thirdParty")
|
||||||
.value("embedUrl", "https://www.youtube.com/watch?v=" + videoId)
|
.value("embedUrl", "https://www.youtube.com/watch?v=" + videoId)
|
||||||
.end()
|
.end()
|
||||||
.object("user")
|
.object("user")
|
||||||
// TO DO: provide a way to enable restricted mode with:
|
// TO DO: provide a way to enable restricted mode with:
|
||||||
// .value("enableSafetyMode", boolean)
|
// .value("enableSafetyMode", boolean)
|
||||||
.value("lockedSafetyMode", false)
|
.value("lockedSafetyMode", false)
|
||||||
.end()
|
.end()
|
||||||
.end();
|
.end();
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
@ -1364,19 +1365,19 @@ public final class YoutubeParsingHelper {
|
||||||
return JsonWriter.string((isTvHtml5DesktopJsonBuilder
|
return JsonWriter.string((isTvHtml5DesktopJsonBuilder
|
||||||
? prepareTvHtml5EmbedJsonBuilder(localization, contentCountry, videoId)
|
? prepareTvHtml5EmbedJsonBuilder(localization, contentCountry, videoId)
|
||||||
: prepareDesktopJsonBuilder(localization, contentCountry))
|
: prepareDesktopJsonBuilder(localization, contentCountry))
|
||||||
.object("playbackContext")
|
.object("playbackContext")
|
||||||
.object("contentPlaybackContext")
|
.object("contentPlaybackContext")
|
||||||
// Signature timestamp from the JavaScript base player is needed to get
|
// Signature timestamp from the JavaScript base player is needed to get
|
||||||
// working obfuscated URLs
|
// working obfuscated URLs
|
||||||
.value("signatureTimestamp", sts)
|
.value("signatureTimestamp", sts)
|
||||||
.value("referer", "https://www.youtube.com/watch?v=" + videoId)
|
.value("referer", "https://www.youtube.com/watch?v=" + videoId)
|
||||||
.end()
|
.end()
|
||||||
.end()
|
.end()
|
||||||
.value(CPN, contentPlaybackNonce)
|
.value(CPN, contentPlaybackNonce)
|
||||||
.value(VIDEO_ID, videoId)
|
.value(VIDEO_ID, videoId)
|
||||||
.value(CONTENT_CHECK_OK, true)
|
.value(CONTENT_CHECK_OK, true)
|
||||||
.value(RACY_CHECK_OK, true)
|
.value(RACY_CHECK_OK, true)
|
||||||
.done())
|
.done())
|
||||||
.getBytes(StandardCharsets.UTF_8);
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
@ -1472,7 +1473,7 @@ public final class YoutubeParsingHelper {
|
||||||
* Returns an unmodifiable {@link Map} containing the {@code X-YouTube-Client-Name} and
|
* Returns an unmodifiable {@link Map} containing the {@code X-YouTube-Client-Name} and
|
||||||
* {@code X-YouTube-Client-Version} headers.
|
* {@code X-YouTube-Client-Version} headers.
|
||||||
*
|
*
|
||||||
* @param name The X-YouTube-Client-Name value.
|
* @param name The X-YouTube-Client-Name value.
|
||||||
* @param version X-YouTube-Client-Version value.
|
* @param version X-YouTube-Client-Version value.
|
||||||
*/
|
*/
|
||||||
private static Map<String, List<String>> getClientHeaders(@Nonnull final String name,
|
private static Map<String, List<String>> getClientHeaders(@Nonnull final String name,
|
||||||
|
@ -1483,6 +1484,7 @@ public final class YoutubeParsingHelper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a map with the required cookie header.
|
* Create a map with the required cookie header.
|
||||||
|
*
|
||||||
* @return A singleton map containing the header.
|
* @return A singleton map containing the header.
|
||||||
*/
|
*/
|
||||||
public static Map<String, List<String>> getCookieHeader() {
|
public static Map<String, List<String>> getCookieHeader() {
|
||||||
|
@ -1801,4 +1803,52 @@ public final class YoutubeParsingHelper {
|
||||||
public static boolean isConsentAccepted() {
|
public static boolean isConsentAccepted() {
|
||||||
return consentAccepted;
|
return consentAccepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Pattern AUDIO_STREAM_TYPE_REGEX =
|
||||||
|
Pattern.compile("&xtags=[\\w%]*acont(?:=|%3D)([a-z]+)(?:=|%3D|:|%3A|&|$)");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the audio track type from a YouTube stream URL.
|
||||||
|
* <p>
|
||||||
|
* The track type is parsed from the {@code xtags} URL parameter
|
||||||
|
* (Example: {@code acont=original:lang=en}).
|
||||||
|
* </p>
|
||||||
|
* @param streamUrl YouTube stream URL
|
||||||
|
* @return {@link AudioTrackType} or {@code null} if no track type was found
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static AudioTrackType extractAudioTrackType(final String streamUrl) {
|
||||||
|
final String xtags;
|
||||||
|
try {
|
||||||
|
xtags = Utils.getQueryValue(new URL(streamUrl), "xtags");
|
||||||
|
} catch (final MalformedURLException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (xtags == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String atype = null;
|
||||||
|
for (final String param : xtags.split(":")) {
|
||||||
|
final String[] kv = param.split("=", 2);
|
||||||
|
if (kv.length > 1 && kv[0].equals("acont")) {
|
||||||
|
atype = kv[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (atype == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (atype) {
|
||||||
|
case "original":
|
||||||
|
return AudioTrackType.ORIGINAL;
|
||||||
|
case "dubbed":
|
||||||
|
return AudioTrackType.DUBBED;
|
||||||
|
case "descriptive":
|
||||||
|
return AudioTrackType.DESCRIPTIVE;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,6 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeThrottlingDecrypter;
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeThrottlingDecrypter;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeChannelLinkHandlerFactory;
|
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeChannelLinkHandlerFactory;
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.AudioTrackType;
|
|
||||||
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
|
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
|
||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
import org.schabi.newpipe.extractor.stream.Frameset;
|
import org.schabi.newpipe.extractor.stream.Frameset;
|
||||||
|
@ -100,7 +99,6 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
@ -812,8 +810,6 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||||
"\\bc\\s*&&\\s*d\\.set\\([^,]+\\s*,\\s*(:encodeURIComponent\\s*\\()([a-zA-Z0-9$]+)\\("
|
"\\bc\\s*&&\\s*d\\.set\\([^,]+\\s*,\\s*(:encodeURIComponent\\s*\\()([a-zA-Z0-9$]+)\\("
|
||||||
};
|
};
|
||||||
private static final String STS_REGEX = "signatureTimestamp[=:](\\d+)";
|
private static final String STS_REGEX = "signatureTimestamp[=:](\\d+)";
|
||||||
private static final Pattern AUDIO_STREAM_TYPE_REGEX =
|
|
||||||
Pattern.compile("&xtags=[\\w%]*acont(?:=|%3D)([a-z]+)(?:=|%3D|:|%3A|&|$)");
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||||
|
@ -1488,20 +1484,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||||
itagItem.setAudioLocale(LocaleCompat.forLanguageTag(
|
itagItem.setAudioLocale(LocaleCompat.forLanguageTag(
|
||||||
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter)));
|
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter)));
|
||||||
}
|
}
|
||||||
|
itagItem.setAudioTrackType(YoutubeParsingHelper.extractAudioTrackType(streamUrl));
|
||||||
try {
|
|
||||||
final String atype = Parser.matchGroup1(AUDIO_STREAM_TYPE_REGEX, streamUrl);
|
|
||||||
switch (atype) {
|
|
||||||
case "original":
|
|
||||||
itagItem.setAudioTrackType(AudioTrackType.ORIGINAL);
|
|
||||||
break;
|
|
||||||
case "dubbed":
|
|
||||||
itagItem.setAudioTrackType(AudioTrackType.DUBBED);
|
|
||||||
break;
|
|
||||||
case "descriptive":
|
|
||||||
itagItem.setAudioTrackType(AudioTrackType.DESCRIPTIVE);
|
|
||||||
}
|
|
||||||
} catch (final Parser.RegexException ignored) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
itagItem.setAudioTrackName(formatData.getObject("audioTrack")
|
itagItem.setAudioTrackName(formatData.getObject("audioTrack")
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
package org.schabi.newpipe.extractor.services.youtube;
|
package org.schabi.newpipe.extractor.services.youtube;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.schabi.newpipe.downloader.DownloaderFactory;
|
import org.schabi.newpipe.downloader.DownloaderFactory;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
import org.schabi.newpipe.extractor.stream.AudioTrackType;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Parser;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class YoutubeParsingHelperTest {
|
public class YoutubeParsingHelperTest {
|
||||||
|
|
||||||
|
@ -48,4 +52,17 @@ public class YoutubeParsingHelperTest {
|
||||||
assertEquals("https://www.infektionsschutz.de/coronavirus-sars-cov-2.html",
|
assertEquals("https://www.infektionsschutz.de/coronavirus-sars-cov-2.html",
|
||||||
YoutubeParsingHelper.extractCachedUrlIfNeeded("https://www.infektionsschutz.de/coronavirus-sars-cov-2.html"));
|
YoutubeParsingHelper.extractCachedUrlIfNeeded("https://www.infektionsschutz.de/coronavirus-sars-cov-2.html"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractAudioTrackType() {
|
||||||
|
final String originalUrl = "https://rr2---sn-4g5lzned.googlevideo.com/videoplayback?expire=1679429648&ei=sLsZZKrICIuR1gLSnYbgAg&ip=2001%3A638%3A102%3A26%3A1a7c%3A106b%3A6e4a%3Adc09&id=o-ALWn2ZwDxUXEZKzlsT_X9iuDjRMSi__SgRXVrVjKZEhc&itag=251&source=youtube&requiressl=yes&mh=nU&mm=31%2C29&mn=sn-4g5lzned%2Csn-4g5edndz&ms=au%2Crdu&mv=m&mvi=2&pl=40&initcwndbps=1740000&spc=H3gIhgXQzBxvKu2MOEmFaaEenC4DKdVUwudTeu3dtKwmq-Xv5g&vprv=1&xtags=acont%3Doriginal%3Alang%3Den&mime=audio%2Fwebm&ns=-lg0OQZL1LZRQO-dzE0W4E4L&gir=yes&clen=3513412&dur=303.681&lmt=1679342942566207&mt=1679407764&fvip=1&keepalive=yes&fexp=24007246&c=WEB&txp=5532434&n=gDLP5pImH9Vr7v&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRAIgPFQ1yX8aoc35sz2eV2-wzNIhTQeOHGCsOmIonmo776kCIFo5k6HZ5kAQ6DycRCAG8jJgk9jNyncILGPrGZMZUuuo&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRQIhANODPaBuc32MWI9gF3Bn1iz3byEn7EwUiXpNLuCcQqW9AiBB88Qrrz2fJCzYKg14_nnGxGQH1Uoi7i31OSrHK6_dGw%3D%3D";
|
||||||
|
final String dubbedUrl = "https://rr2---sn-4g5lzned.googlevideo.com/videoplayback?expire=1679429648&ei=sLsZZKrICIuR1gLSnYbgAg&ip=2001%3A638%3A102%3A26%3A1a7c%3A106b%3A6e4a%3Adc09&id=o-ALWn2ZwDxUXEZKzlsT_X9iuDjRMSi__SgRXVrVjKZEhc&itag=251&source=youtube&requiressl=yes&mh=nU&mm=31%2C29&mn=sn-4g5lzned%2Csn-4g5edndz&ms=au%2Crdu&mv=m&mvi=2&pl=40&initcwndbps=1740000&spc=H3gIhgXQzBxvKu2MOEmFaaEenC4DKdVUwudTeu3dtKwmq-Xv5g&vprv=1&xtags=acont%3Ddubbed%3Alang%3Den&mime=audio%2Fwebm&ns=-lg0OQZL1LZRQO-dzE0W4E4L&gir=yes&clen=3884070&dur=303.721&lmt=1679342946044954&mt=1679407764&fvip=1&keepalive=yes&fexp=24007246&c=WEB&txp=5532434&n=gDLP5pImH9Vr7v&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRQIhAKEMLB8yLZJf2jXAu4P1Q8AVEciYsmjjr2syYAWZfJg6AiAfu-XI11zYpCLqljw_MCegh26pJHYyfatgfFGWfpL-6Q%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRQIhANODPaBuc32MWI9gF3Bn1iz3byEn7EwUiXpNLuCcQqW9AiBB88Qrrz2fJCzYKg14_nnGxGQH1Uoi7i31OSrHK6_dGw%3D%3D";
|
||||||
|
final String descriptiveUrl = "https://rr2---sn-4g5lzned.googlevideo.com/videoplayback?expire=1679429648&ei=sLsZZKrICIuR1gLSnYbgAg&ip=2001%3A638%3A102%3A26%3A1a7c%3A106b%3A6e4a%3Adc09&id=o-ALWn2ZwDxUXEZKzlsT_X9iuDjRMSi__SgRXVrVjKZEhc&itag=251&source=youtube&requiressl=yes&mh=nU&mm=31%2C29&mn=sn-4g5lzned%2Csn-4g5edndz&ms=au%2Crdu&mv=m&mvi=2&pl=40&initcwndbps=1740000&spc=H3gIhgXQzBxvKu2MOEmFaaEenC4DKdVUwudTeu3dtKwmq-Xv5g&vprv=1&xtags=acont%3Ddescriptive%3Alang%3Den&mime=audio%2Fwebm&ns=-lg0OQZL1LZRQO-dzE0W4E4L&gir=yes&clen=4061711&dur=303.721&lmt=1679342946800120&mt=1679407764&fvip=1&keepalive=yes&fexp=24007246&c=WEB&txp=5532434&n=gDLP5pImH9Vr7v&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRgIhAKFUzoNscV1hbNcPwcnQO3vOy47q69szj7BdLhFYS52pAiEA2oPhLZIZsrUQrx62iH4dHvTBlCloC3NieJw6edo7LL8%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRQIhANODPaBuc32MWI9gF3Bn1iz3byEn7EwUiXpNLuCcQqW9AiBB88Qrrz2fJCzYKg14_nnGxGQH1Uoi7i31OSrHK6_dGw%3D%3D";
|
||||||
|
final String noTrackUrl = "https://rr2---sn-4g5ednz7.googlevideo.com/videoplayback?expire=1679430240&ei=AL4ZZKiXJefYx_APj_6ECA&ip=2001%3A638%3A102%3A26%3A1a7c%3A106b%3A6e4a%3Adc09&id=o-ALKVh9uHVEvurL3bZOZCEMzFod9ZmJJd6GszA6UEIuKy&itag=251&source=youtube&requiressl=yes&mh=8L&mm=31%2C26&mn=sn-4g5ednz7%2Csn-i5heen7z&ms=au%2Conr&mv=m&mvi=2&pl=40&initcwndbps=1793750&spc=H3gIhh2s06nxQJg3zEgY9pw84syUasRiagYDsQ5UHHfcu5bfTA&vprv=1&mime=audio%2Fwebm&ns=VumObYcnTZNicexX7Ek2WakL&gir=yes&clen=3711099&dur=299.201&lmt=1679334484198077&mt=1679408487&fvip=2&keepalive=yes&fexp=24007246&c=WEB&txp=3318224&n=10c-m6ZvG6C7rC&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRQIhAODS0aHRBgdrHm5qwquqGC6zq3rU81W59y4BtV0Y9KStAiAPT8ykXXj_7GzAyZbLPgYKs-B1HWT-4bY0CppmZ2rReg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRQIhAL8fS6T-V9BNqrx55mdMvve5be2gcjIY8pYfxlUMPY6pAiAgiCMbqR4eSS_HvLu9KBe6cCFZeMcSTc7vzWtL9y0xvw%3D%3D";
|
||||||
|
|
||||||
|
assertEquals(AudioTrackType.ORIGINAL, YoutubeParsingHelper.extractAudioTrackType(originalUrl));
|
||||||
|
assertEquals(AudioTrackType.DUBBED, YoutubeParsingHelper.extractAudioTrackType(dubbedUrl));
|
||||||
|
assertEquals(AudioTrackType.DESCRIPTIVE, YoutubeParsingHelper.extractAudioTrackType(descriptiveUrl));
|
||||||
|
assertNull(YoutubeParsingHelper.extractAudioTrackType(noTrackUrl));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue