mirror of
https://github.com/TeamNewPipe/NewPipeExtractor.git
synced 2024-12-14 22:30:33 +05:30
commit
a8665fd97f
@ -3,7 +3,6 @@ package org.schabi.newpipe.extractor.services.youtube.extractors;
|
|||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
|
|
||||||
import org.mozilla.javascript.Context;
|
import org.mozilla.javascript.Context;
|
||||||
import org.mozilla.javascript.Function;
|
import org.mozilla.javascript.Function;
|
||||||
import org.mozilla.javascript.ScriptableObject;
|
import org.mozilla.javascript.ScriptableObject;
|
||||||
@ -36,6 +35,8 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
|
|||||||
import org.schabi.newpipe.extractor.utils.Parser;
|
import org.schabi.newpipe.extractor.utils.Parser;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -49,9 +50,6 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonResponse;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonResponse;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
@ -84,8 +82,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
// Exceptions
|
// Exceptions
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public class DecryptException extends ParsingException {
|
public class DeobfuscateException extends ParsingException {
|
||||||
DecryptException(String message, Throwable cause) {
|
DeobfuscateException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,12 +154,14 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.fromLocalizationCode("en"));
|
TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.fromLocalizationCode("en"));
|
||||||
Calendar parsedTime = timeAgoParser.parse(time).date();
|
Calendar parsedTime = timeAgoParser.parse(time).date();
|
||||||
return new SimpleDateFormat("yyyy-MM-dd").format(parsedTime.getTime());
|
return new SimpleDateFormat("yyyy-MM-dd").format(parsedTime.getTime());
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
try { // Premiered Feb 21, 2020
|
try { // Premiered Feb 21, 2020
|
||||||
Date d = new SimpleDateFormat("MMM dd, YYYY", Locale.ENGLISH).parse(time);
|
Date d = new SimpleDateFormat("MMM dd, YYYY", Locale.ENGLISH).parse(time);
|
||||||
return new SimpleDateFormat("yyyy-MM-dd").format(d.getTime());
|
return new SimpleDateFormat("yyyy-MM-dd").format(d.getTime());
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -169,7 +169,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
Date d = new SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH).parse(
|
Date d = new SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH).parse(
|
||||||
getTextFromObject(getVideoPrimaryInfoRenderer().getObject("dateText")));
|
getTextFromObject(getVideoPrimaryInfoRenderer().getObject("dateText")));
|
||||||
return new SimpleDateFormat("yyyy-MM-dd").format(d);
|
return new SimpleDateFormat("yyyy-MM-dd").format(d);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
throw new ParsingException("Could not get upload date");
|
throw new ParsingException("Could not get upload date");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,7 +369,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
try {
|
try {
|
||||||
uploaderName = getTextFromObject(getVideoSecondaryInfoRenderer().getObject("owner")
|
uploaderName = getTextFromObject(getVideoSecondaryInfoRenderer().getObject("owner")
|
||||||
.getObject("videoOwnerRenderer").getObject("title"));
|
.getObject("videoOwnerRenderer").getObject("title"));
|
||||||
} catch (ParsingException ignored) { }
|
} catch (ParsingException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
if (isNullOrEmpty(uploaderName)) {
|
if (isNullOrEmpty(uploaderName)) {
|
||||||
uploaderName = playerResponse.getObject("videoDetails").getString("author");
|
uploaderName = playerResponse.getObject("videoDetails").getString("author");
|
||||||
@ -436,11 +438,11 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!dashManifestUrl.contains("/signature/")) {
|
if (!dashManifestUrl.contains("/signature/")) {
|
||||||
String encryptedSig = Parser.matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifestUrl);
|
String obfuscatedSig = Parser.matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifestUrl);
|
||||||
String decryptedSig;
|
String deobfuscatedSig;
|
||||||
|
|
||||||
decryptedSig = decryptSignature(encryptedSig, decryptionCode);
|
deobfuscatedSig = deobfuscateSignature(obfuscatedSig, deobfuscationCode);
|
||||||
dashManifestUrl = dashManifestUrl.replace("/s/" + encryptedSig, "/signature/" + decryptedSig);
|
dashManifestUrl = dashManifestUrl.replace("/s/" + obfuscatedSig, "/signature/" + deobfuscatedSig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dashManifestUrl;
|
return dashManifestUrl;
|
||||||
@ -630,7 +632,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
private static final String FORMATS = "formats";
|
private static final String FORMATS = "formats";
|
||||||
private static final String ADAPTIVE_FORMATS = "adaptiveFormats";
|
private static final String ADAPTIVE_FORMATS = "adaptiveFormats";
|
||||||
private static final String HTTPS = "https:";
|
private static final String HTTPS = "https:";
|
||||||
private static final String DECRYPTION_FUNC_NAME = "decrypt";
|
private static final String DEOBFUSCATION_FUNC_NAME = "decrypt";
|
||||||
|
|
||||||
private final static String[] REGEXES = {
|
private final static String[] REGEXES = {
|
||||||
"(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)",
|
"(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)",
|
||||||
@ -640,7 +642,7 @@ 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 volatile String decryptionCode = "";
|
private volatile String deobfuscationCode = "";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
|
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
|
||||||
@ -695,8 +697,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
throw new ContentNotAvailableException("Got error: \"" + reason + "\"");
|
throw new ContentNotAvailableException("Got error: \"" + reason + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decryptionCode.isEmpty()) {
|
if (deobfuscationCode.isEmpty()) {
|
||||||
decryptionCode = loadDecryptionCode(playerUrl);
|
deobfuscationCode = loadDeobfuscationCode(playerUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subtitlesInfos.isEmpty()) {
|
if (subtitlesInfos.isEmpty()) {
|
||||||
@ -716,7 +718,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
private String getPlayerUrl(final JsonObject playerConfig) throws ParsingException {
|
private String getPlayerUrl(final JsonObject playerConfig) throws ParsingException {
|
||||||
// The Youtube service needs to be initialized by downloading the
|
// The Youtube service needs to be initialized by downloading the
|
||||||
// js-Youtube-player. This is done in order to get the algorithm
|
// js-Youtube-player. This is done in order to get the algorithm
|
||||||
// for decrypting cryptic signatures inside certain stream URLs.
|
// for deobfuscating cryptic signatures inside certain stream URLs.
|
||||||
final String playerUrl = playerConfig.getObject("assets").getString("js");
|
final String playerUrl = playerConfig.getObject("assets").getString("js");
|
||||||
|
|
||||||
if (playerUrl == null) {
|
if (playerUrl == null) {
|
||||||
@ -768,11 +770,11 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ParsingException(
|
throw new ParsingException(
|
||||||
"Could load decryption code form restricted video for the Youtube service.", e);
|
"Could load deobfuscation code form restricted video for the Youtube service.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String loadDecryptionCode(String playerUrl) throws DecryptException {
|
private String loadDeobfuscationCode(String playerUrl) throws DeobfuscateException {
|
||||||
try {
|
try {
|
||||||
Downloader downloader = NewPipe.getDownloader();
|
Downloader downloader = NewPipe.getDownloader();
|
||||||
if (!playerUrl.contains("https://youtube.com")) {
|
if (!playerUrl.contains("https://youtube.com")) {
|
||||||
@ -782,49 +784,49 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String playerCode = downloader.get(playerUrl, getExtractorLocalization()).responseBody();
|
final String playerCode = downloader.get(playerUrl, getExtractorLocalization()).responseBody();
|
||||||
final String decryptionFunctionName = getDecryptionFuncName(playerCode);
|
final String deobfuscationFunctionName = getDeobfuscationFuncName(playerCode);
|
||||||
|
|
||||||
final String functionPattern = "("
|
final String functionPattern = "("
|
||||||
+ decryptionFunctionName.replace("$", "\\$")
|
+ deobfuscationFunctionName.replace("$", "\\$")
|
||||||
+ "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})";
|
+ "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})";
|
||||||
final String decryptionFunction = "var " + Parser.matchGroup1(functionPattern, playerCode) + ";";
|
final String deobfuscateFunction = "var " + Parser.matchGroup1(functionPattern, playerCode) + ";";
|
||||||
|
|
||||||
final String helperObjectName =
|
final String helperObjectName =
|
||||||
Parser.matchGroup1(";([A-Za-z0-9_\\$]{2})\\...\\(", decryptionFunction);
|
Parser.matchGroup1(";([A-Za-z0-9_\\$]{2})\\...\\(", deobfuscateFunction);
|
||||||
final String helperPattern =
|
final String helperPattern =
|
||||||
"(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)";
|
"(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)";
|
||||||
final String helperObject =
|
final String helperObject =
|
||||||
Parser.matchGroup1(helperPattern, playerCode.replace("\n", ""));
|
Parser.matchGroup1(helperPattern, playerCode.replace("\n", ""));
|
||||||
|
|
||||||
final String callerFunction =
|
final String callerFunction =
|
||||||
"function " + DECRYPTION_FUNC_NAME + "(a){return " + decryptionFunctionName + "(a);}";
|
"function " + DEOBFUSCATION_FUNC_NAME + "(a){return " + deobfuscationFunctionName + "(a);}";
|
||||||
|
|
||||||
return helperObject + decryptionFunction + callerFunction;
|
return helperObject + deobfuscateFunction + callerFunction;
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new DecryptException("Could not load decrypt function", ioe);
|
throw new DeobfuscateException("Could not load deobfuscate function", ioe);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new DecryptException("Could not parse decrypt function ", e);
|
throw new DeobfuscateException("Could not parse deobfuscate function ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String decryptSignature(String encryptedSig, String decryptionCode) throws DecryptException {
|
private String deobfuscateSignature(String obfuscatedSig, String deobfuscationCode) throws DeobfuscateException {
|
||||||
final Context context = Context.enter();
|
final Context context = Context.enter();
|
||||||
context.setOptimizationLevel(-1);
|
context.setOptimizationLevel(-1);
|
||||||
final Object result;
|
final Object result;
|
||||||
try {
|
try {
|
||||||
final ScriptableObject scope = context.initSafeStandardObjects();
|
final ScriptableObject scope = context.initSafeStandardObjects();
|
||||||
context.evaluateString(scope, decryptionCode, "decryptionCode", 1, null);
|
context.evaluateString(scope, deobfuscationCode, "decryptionCode", 1, null);
|
||||||
final Function decryptionFunc = (Function) scope.get("decrypt", scope);
|
final Function deobfuscateFunc = (Function) scope.get("decrypt", scope);
|
||||||
result = decryptionFunc.call(context, scope, scope, new Object[]{encryptedSig});
|
result = deobfuscateFunc.call(context, scope, scope, new Object[]{obfuscatedSig});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new DecryptException("Could not get decrypt signature", e);
|
throw new DeobfuscateException("Could not get deobfuscate signature", e);
|
||||||
} finally {
|
} finally {
|
||||||
Context.exit();
|
Context.exit();
|
||||||
}
|
}
|
||||||
return result == null ? "" : result.toString();
|
return result == null ? "" : result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDecryptionFuncName(final String playerCode) throws DecryptException {
|
private String getDeobfuscationFuncName(final String playerCode) throws DeobfuscateException {
|
||||||
Parser.RegexException exception = null;
|
Parser.RegexException exception = null;
|
||||||
for (final String regex : REGEXES) {
|
for (final String regex : REGEXES) {
|
||||||
try {
|
try {
|
||||||
@ -835,7 +837,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new DecryptException("Could not find decrypt function with any of the given patterns.", exception);
|
throw new DeobfuscateException("Could not find deobfuscate function with any of the given patterns.", exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -989,18 +991,19 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
if (formatData.has("url")) {
|
if (formatData.has("url")) {
|
||||||
streamUrl = formatData.getString("url");
|
streamUrl = formatData.getString("url");
|
||||||
} else {
|
} else {
|
||||||
// this url has an encrypted signature
|
// this url has an obfuscated signature
|
||||||
final String cipherString = formatData.has("cipher")
|
final String cipherString = formatData.has("cipher")
|
||||||
? formatData.getString("cipher")
|
? formatData.getString("cipher")
|
||||||
: formatData.getString("signatureCipher");
|
: formatData.getString("signatureCipher");
|
||||||
final Map<String, String> cipher = Parser.compatParseMap(cipherString);
|
final Map<String, String> cipher = Parser.compatParseMap(cipherString);
|
||||||
streamUrl = cipher.get("url") + "&" + cipher.get("sp") + "="
|
streamUrl = cipher.get("url") + "&" + cipher.get("sp") + "="
|
||||||
+ decryptSignature(cipher.get("s"), decryptionCode);
|
+ deobfuscateSignature(cipher.get("s"), deobfuscationCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
urlAndItags.put(streamUrl, itagItem);
|
urlAndItags.put(streamUrl, itagItem);
|
||||||
}
|
}
|
||||||
} catch (UnsupportedEncodingException ignored) {}
|
} catch (UnsupportedEncodingException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
|||||||
|
|
||||||
public class SoundcloudStreamExtractorTest {
|
public class SoundcloudStreamExtractorTest {
|
||||||
|
|
||||||
public static class LilUziVertDoWhatIWant extends DefaultStreamExtractorTest {
|
public static class CreativeCommonsPlaysWellWithOthers extends DefaultStreamExtractorTest {
|
||||||
private static final String ID = "do-what-i-want-produced-by-maaly-raw-don-cannon";
|
private static final String ID = "plays-well-with-others-ep-2-what-do-an-army-of-ants-and-an-online-encyclopedia-have-in-common";
|
||||||
private static final String UPLOADER = "https://soundcloud.com/liluzivert";
|
private static final String UPLOADER = "https://soundcloud.com/wearecc";
|
||||||
private static final int TIMESTAMP = 69;
|
private static final int TIMESTAMP = 69;
|
||||||
private static final String URL = UPLOADER + "/" + ID + "#t=" + TIMESTAMP;
|
private static final String URL = UPLOADER + "/" + ID + "#t=" + TIMESTAMP;
|
||||||
private static StreamExtractor extractor;
|
private static StreamExtractor extractor;
|
||||||
@ -35,20 +35,21 @@ public class SoundcloudStreamExtractorTest {
|
|||||||
|
|
||||||
@Override public StreamExtractor extractor() { return extractor; }
|
@Override public StreamExtractor extractor() { return extractor; }
|
||||||
@Override public StreamingService expectedService() { return SoundCloud; }
|
@Override public StreamingService expectedService() { return SoundCloud; }
|
||||||
@Override public String expectedName() { return "Do What I Want [Produced By Maaly Raw + Don Cannon]"; }
|
@Override public String expectedName() { return "Plays Well with Others, Ep 2: What Do an Army of Ants and an Online Encyclopedia Have in Common?"; }
|
||||||
@Override public String expectedId() { return "276206960"; }
|
@Override public String expectedId() { return "597253485"; }
|
||||||
@Override public String expectedUrlContains() { return UPLOADER + "/" + ID; }
|
@Override public String expectedUrlContains() { return UPLOADER + "/" + ID; }
|
||||||
@Override public String expectedOriginalUrlContains() { return URL; }
|
@Override public String expectedOriginalUrlContains() { return URL; }
|
||||||
|
|
||||||
@Override public StreamType expectedStreamType() { return StreamType.AUDIO_STREAM; }
|
@Override public StreamType expectedStreamType() { return StreamType.AUDIO_STREAM; }
|
||||||
@Override public String expectedUploaderName() { return "Lil Uzi Vert"; }
|
@Override public String expectedUploaderName() { return "Creative Commons"; }
|
||||||
@Override public String expectedUploaderUrl() { return UPLOADER; }
|
@Override public String expectedUploaderUrl() { return UPLOADER; }
|
||||||
@Override public List<String> expectedDescriptionContains() { return Arrays.asList("The Perfect LUV Tape®"); }
|
@Override public List<String> expectedDescriptionContains() { return Arrays.asList("Stigmergy is a mechanism of indirect coordination",
|
||||||
@Override public long expectedLength() { return 175; }
|
"All original content in Plays Well with Others is available under a Creative Commons BY license."); }
|
||||||
|
@Override public long expectedLength() { return 1400; }
|
||||||
@Override public long expectedTimestamp() { return TIMESTAMP; }
|
@Override public long expectedTimestamp() { return TIMESTAMP; }
|
||||||
@Override public long expectedViewCountAtLeast() { return 75413600; }
|
@Override public long expectedViewCountAtLeast() { return 27000; }
|
||||||
@Nullable @Override public String expectedUploadDate() { return "2016-07-31 18:18:07.000"; }
|
@Nullable @Override public String expectedUploadDate() { return "2019-03-28 13:36:18.000"; }
|
||||||
@Nullable @Override public String expectedTextualUploadDate() { return "2016-07-31 18:18:07"; }
|
@Nullable @Override public String expectedTextualUploadDate() { return "2019-03-28 13:36:18"; }
|
||||||
@Override public long expectedLikeCountAtLeast() { return -1; }
|
@Override public long expectedLikeCountAtLeast() { return -1; }
|
||||||
@Override public long expectedDislikeCountAtLeast() { return -1; }
|
@Override public long expectedDislikeCountAtLeast() { return -1; }
|
||||||
@Override public boolean expectedHasVideoStreams() { return false; }
|
@Override public boolean expectedHasVideoStreams() { return false; }
|
||||||
@ -56,26 +57,4 @@ public class SoundcloudStreamExtractorTest {
|
|||||||
@Override public boolean expectedHasFrames() { return false; }
|
@Override public boolean expectedHasFrames() { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ContentNotSupported {
|
|
||||||
@BeforeClass
|
|
||||||
public static void setUp() {
|
|
||||||
NewPipe.init(DownloaderTestImpl.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = ContentNotSupportedException.class)
|
|
||||||
public void hlsAudioStream() throws Exception {
|
|
||||||
final StreamExtractor extractor =
|
|
||||||
SoundCloud.getStreamExtractor("https://soundcloud.com/dualipa/cool");
|
|
||||||
extractor.fetchPage();
|
|
||||||
extractor.getAudioStreams();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = ContentNotSupportedException.class)
|
|
||||||
public void bothHlsAndOpusAudioStreams() throws Exception {
|
|
||||||
final StreamExtractor extractor =
|
|
||||||
SoundCloud.getStreamExtractor("https://soundcloud.com/lil-baby-4pf/no-sucker");
|
|
||||||
extractor.fetchPage();
|
|
||||||
extractor.getAudioStreams();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -432,190 +432,6 @@ public class YoutubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this channel has no "Subscribe" button
|
|
||||||
public static class EminemVEVO implements BaseChannelExtractorTest {
|
|
||||||
private static YoutubeChannelExtractor extractor;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setUp() throws Exception {
|
|
||||||
NewPipe.init(DownloaderTestImpl.getInstance());
|
|
||||||
extractor = (YoutubeChannelExtractor) YouTube
|
|
||||||
.getChannelExtractor("https://www.youtube.com/user/EminemVEVO/");
|
|
||||||
extractor.fetchPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Extractor
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testServiceId() {
|
|
||||||
assertEquals(YouTube.getServiceId(), extractor.getServiceId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testName() throws Exception {
|
|
||||||
assertEquals("EminemVEVO", extractor.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testId() throws Exception {
|
|
||||||
assertEquals("UC20vb-R_px4CguHzzBPhoyQ", extractor.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUrl() throws ParsingException {
|
|
||||||
assertEquals("https://www.youtube.com/channel/UC20vb-R_px4CguHzzBPhoyQ", extractor.getUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOriginalUrl() throws ParsingException {
|
|
||||||
assertEquals("https://www.youtube.com/user/EminemVEVO/", extractor.getOriginalUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// ListExtractor
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRelatedItems() throws Exception {
|
|
||||||
defaultTestRelatedItems(extractor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMoreRelatedItems() throws Exception {
|
|
||||||
defaultTestMoreItems(extractor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// ChannelExtractor
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDescription() throws Exception {
|
|
||||||
final String description = extractor.getDescription();
|
|
||||||
assertTrue(description, description.contains("Eminem on Vevo"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAvatarUrl() throws Exception {
|
|
||||||
String avatarUrl = extractor.getAvatarUrl();
|
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertTrue(avatarUrl, avatarUrl.contains("yt3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBannerUrl() throws Exception {
|
|
||||||
String bannerUrl = extractor.getBannerUrl();
|
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
assertTrue(bannerUrl, bannerUrl.contains("yt3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFeedUrl() throws Exception {
|
|
||||||
assertEquals("https://www.youtube.com/feeds/videos.xml?channel_id=UC20vb-R_px4CguHzzBPhoyQ", extractor.getFeedUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSubscriberCount() throws Exception {
|
|
||||||
// there is no "Subscribe" button
|
|
||||||
long subscribers = extractor.getSubscriberCount();
|
|
||||||
assertEquals("Wrong subscriber count", -1, subscribers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some VEVO channels will redirect to a new page with a new channel id.
|
|
||||||
* <p>
|
|
||||||
* Though, it isn't a simple redirect, but a redirect instruction embed in the response itself, this
|
|
||||||
* test assure that we account for that.
|
|
||||||
*/
|
|
||||||
public static class RedirectedChannel implements BaseChannelExtractorTest {
|
|
||||||
private static YoutubeChannelExtractor extractor;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setUp() throws Exception {
|
|
||||||
NewPipe.init(DownloaderTestImpl.getInstance());
|
|
||||||
extractor = (YoutubeChannelExtractor) YouTube
|
|
||||||
.getChannelExtractor("https://www.youtube.com/channel/UCITk7Ky4iE5_xISw9IaHqpQ");
|
|
||||||
extractor.fetchPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Extractor
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testServiceId() {
|
|
||||||
assertEquals(YouTube.getServiceId(), extractor.getServiceId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testName() throws Exception {
|
|
||||||
assertEquals("LordiVEVO", extractor.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testId() throws Exception {
|
|
||||||
assertEquals("UCrxkwepj7-4Wz1wHyfzw-sQ", extractor.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUrl() throws ParsingException {
|
|
||||||
assertEquals("https://www.youtube.com/channel/UCrxkwepj7-4Wz1wHyfzw-sQ", extractor.getUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOriginalUrl() throws ParsingException {
|
|
||||||
assertEquals("https://www.youtube.com/channel/UCITk7Ky4iE5_xISw9IaHqpQ", extractor.getOriginalUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// ListExtractor
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRelatedItems() throws Exception {
|
|
||||||
defaultTestRelatedItems(extractor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMoreRelatedItems() throws Exception {
|
|
||||||
assertNoMoreItems(extractor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// ChannelExtractor
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDescription() throws Exception {
|
|
||||||
assertEmpty(extractor.getDescription());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAvatarUrl() throws Exception {
|
|
||||||
String avatarUrl = extractor.getAvatarUrl();
|
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertTrue(avatarUrl, avatarUrl.contains("yt3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBannerUrl() throws Exception {
|
|
||||||
assertEmpty(extractor.getBannerUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFeedUrl() throws Exception {
|
|
||||||
assertEquals("https://www.youtube.com/feeds/videos.xml?channel_id=UCrxkwepj7-4Wz1wHyfzw-sQ", extractor.getFeedUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSubscriberCount() throws Exception {
|
|
||||||
assertEquals(-1, extractor.getSubscriberCount());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class RandomChannel implements BaseChannelExtractorTest {
|
public static class RandomChannel implements BaseChannelExtractorTest {
|
||||||
private static YoutubeChannelExtractor extractor;
|
private static YoutubeChannelExtractor extractor;
|
||||||
|
|
||||||
|
@ -153,8 +153,8 @@ public class YoutubeMusicSearchExtractorTest {
|
|||||||
|
|
||||||
public static class CorrectedSearch extends DefaultSearchExtractorTest {
|
public static class CorrectedSearch extends DefaultSearchExtractorTest {
|
||||||
private static SearchExtractor extractor;
|
private static SearchExtractor extractor;
|
||||||
private static final String QUERY = "duo lipa";
|
private static final String QUERY = "nocopyrigh sounds";
|
||||||
private static final String EXPECTED_SUGGESTION = "dua lipa";
|
private static final String EXPECTED_SUGGESTION = "nocopyrightsounds";
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setUp() throws Exception {
|
public static void setUp() throws Exception {
|
||||||
|
@ -62,48 +62,6 @@ public class YoutubeStreamExtractorDefaultTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AdeleHello extends DefaultStreamExtractorTest {
|
|
||||||
private static final String ID = "YQHsXMglC9A";
|
|
||||||
private static final String URL = BASE_URL + ID;
|
|
||||||
private static StreamExtractor extractor;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setUp() throws Exception {
|
|
||||||
NewPipe.init(DownloaderTestImpl.getInstance());
|
|
||||||
extractor = YouTube.getStreamExtractor(URL);
|
|
||||||
extractor.fetchPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
public void testUploaderUrl() throws ParsingException {
|
|
||||||
String url = extractor().getUploaderUrl();
|
|
||||||
if (!url.equals("https://www.youtube.com/channel/UCsRM0YB_dabtEPGPTKo-gcw") &&
|
|
||||||
!url.equals("https://www.youtube.com/channel/UComP_epzeKzvBX156r6pm1Q")) {
|
|
||||||
fail("Uploader url is neither the music channel one nor the Vevo one");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public StreamExtractor extractor() { return extractor; }
|
|
||||||
@Override public StreamingService expectedService() { return YouTube; }
|
|
||||||
@Override public String expectedName() { return "Adele - Hello"; }
|
|
||||||
@Override public String expectedId() { return ID; }
|
|
||||||
@Override public String expectedUrlContains() { return URL; }
|
|
||||||
@Override public String expectedOriginalUrlContains() { return URL; }
|
|
||||||
|
|
||||||
@Override public StreamType expectedStreamType() { return StreamType.VIDEO_STREAM; }
|
|
||||||
@Override public String expectedUploaderName() { return "Adele"; }
|
|
||||||
@Override public String expectedUploaderUrl() { return null; } // overridden above
|
|
||||||
@Override public List<String> expectedDescriptionContains() { return Arrays.asList("http://adele.com", "https://www.facebook.com/Adele"); }
|
|
||||||
@Override public long expectedLength() { return 367; }
|
|
||||||
@Override public long expectedViewCountAtLeast() { return 1220025784; }
|
|
||||||
@Nullable @Override public String expectedUploadDate() { return "2015-10-22 00:00:00.000"; }
|
|
||||||
@Nullable @Override public String expectedTextualUploadDate() { return "2015-10-22"; }
|
|
||||||
@Override public long expectedLikeCountAtLeast() { return 15289000; }
|
|
||||||
@Override public long expectedDislikeCountAtLeast() { return 826000; }
|
|
||||||
@Override public boolean expectedHasSubtitles() { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DescriptionTestPewdiepie extends DefaultStreamExtractorTest {
|
public static class DescriptionTestPewdiepie extends DefaultStreamExtractorTest {
|
||||||
private static final String ID = "7PIMiDcwNvc";
|
private static final String ID = "7PIMiDcwNvc";
|
||||||
private static final int TIMESTAMP = 17;
|
private static final int TIMESTAMP = 17;
|
||||||
|
Loading…
Reference in New Issue
Block a user