diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index 0077a163..8b60a41e 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.extractor.services.youtube.extractors; import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; - import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; 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.Utils; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; @@ -49,9 +50,6 @@ import java.util.List; import java.util.Locale; 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.getJsonResponse; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; @@ -84,8 +82,8 @@ public class YoutubeStreamExtractor extends StreamExtractor { // Exceptions //////////////////////////////////////////////////////////////////////////*/ - public class DecryptException extends ParsingException { - DecryptException(String message, Throwable cause) { + public class DeobfuscateException extends ParsingException { + DeobfuscateException(String message, Throwable cause) { super(message, cause); } } @@ -156,12 +154,14 @@ public class YoutubeStreamExtractor extends StreamExtractor { TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.fromLocalizationCode("en")); Calendar parsedTime = timeAgoParser.parse(time).date(); return new SimpleDateFormat("yyyy-MM-dd").format(parsedTime.getTime()); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } try { // Premiered Feb 21, 2020 Date d = new SimpleDateFormat("MMM dd, YYYY", Locale.ENGLISH).parse(time); return new SimpleDateFormat("yyyy-MM-dd").format(d.getTime()); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } } try { @@ -169,7 +169,8 @@ public class YoutubeStreamExtractor extends StreamExtractor { Date d = new SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH).parse( getTextFromObject(getVideoPrimaryInfoRenderer().getObject("dateText"))); return new SimpleDateFormat("yyyy-MM-dd").format(d); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } throw new ParsingException("Could not get upload date"); } @@ -368,7 +369,8 @@ public class YoutubeStreamExtractor extends StreamExtractor { try { uploaderName = getTextFromObject(getVideoSecondaryInfoRenderer().getObject("owner") .getObject("videoOwnerRenderer").getObject("title")); - } catch (ParsingException ignored) { } + } catch (ParsingException ignored) { + } if (isNullOrEmpty(uploaderName)) { uploaderName = playerResponse.getObject("videoDetails").getString("author"); @@ -436,11 +438,11 @@ public class YoutubeStreamExtractor extends StreamExtractor { } if (!dashManifestUrl.contains("/signature/")) { - String encryptedSig = Parser.matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifestUrl); - String decryptedSig; + String obfuscatedSig = Parser.matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifestUrl); + String deobfuscatedSig; - decryptedSig = decryptSignature(encryptedSig, decryptionCode); - dashManifestUrl = dashManifestUrl.replace("/s/" + encryptedSig, "/signature/" + decryptedSig); + deobfuscatedSig = deobfuscateSignature(obfuscatedSig, deobfuscationCode); + dashManifestUrl = dashManifestUrl.replace("/s/" + obfuscatedSig, "/signature/" + deobfuscatedSig); } return dashManifestUrl; @@ -630,7 +632,7 @@ public class YoutubeStreamExtractor extends StreamExtractor { private static final String FORMATS = "formats"; private static final String ADAPTIVE_FORMATS = "adaptiveFormats"; 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 = { "(?:\\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$]+)\\(" }; - private volatile String decryptionCode = ""; + private volatile String deobfuscationCode = ""; @Override public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException { @@ -695,8 +697,8 @@ public class YoutubeStreamExtractor extends StreamExtractor { throw new ContentNotAvailableException("Got error: \"" + reason + "\""); } - if (decryptionCode.isEmpty()) { - decryptionCode = loadDecryptionCode(playerUrl); + if (deobfuscationCode.isEmpty()) { + deobfuscationCode = loadDeobfuscationCode(playerUrl); } if (subtitlesInfos.isEmpty()) { @@ -716,7 +718,7 @@ public class YoutubeStreamExtractor extends StreamExtractor { private String getPlayerUrl(final JsonObject playerConfig) throws ParsingException { // The Youtube service needs to be initialized by downloading the // 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"); if (playerUrl == null) { @@ -768,11 +770,11 @@ public class YoutubeStreamExtractor extends StreamExtractor { } catch (IOException e) { 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 { Downloader downloader = NewPipe.getDownloader(); 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 decryptionFunctionName = getDecryptionFuncName(playerCode); + final String deobfuscationFunctionName = getDeobfuscationFuncName(playerCode); final String functionPattern = "(" - + decryptionFunctionName.replace("$", "\\$") + + deobfuscationFunctionName.replace("$", "\\$") + "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})"; - final String decryptionFunction = "var " + Parser.matchGroup1(functionPattern, playerCode) + ";"; + final String deobfuscateFunction = "var " + Parser.matchGroup1(functionPattern, playerCode) + ";"; final String helperObjectName = - Parser.matchGroup1(";([A-Za-z0-9_\\$]{2})\\...\\(", decryptionFunction); + Parser.matchGroup1(";([A-Za-z0-9_\\$]{2})\\...\\(", deobfuscateFunction); final String helperPattern = "(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)"; final String helperObject = Parser.matchGroup1(helperPattern, playerCode.replace("\n", "")); 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) { - throw new DecryptException("Could not load decrypt function", ioe); + throw new DeobfuscateException("Could not load deobfuscate function", ioe); } 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(); context.setOptimizationLevel(-1); final Object result; try { final ScriptableObject scope = context.initSafeStandardObjects(); - context.evaluateString(scope, decryptionCode, "decryptionCode", 1, null); - final Function decryptionFunc = (Function) scope.get("decrypt", scope); - result = decryptionFunc.call(context, scope, scope, new Object[]{encryptedSig}); + context.evaluateString(scope, deobfuscationCode, "decryptionCode", 1, null); + final Function deobfuscateFunc = (Function) scope.get("decrypt", scope); + result = deobfuscateFunc.call(context, scope, scope, new Object[]{obfuscatedSig}); } catch (Exception e) { - throw new DecryptException("Could not get decrypt signature", e); + throw new DeobfuscateException("Could not get deobfuscate signature", e); } finally { Context.exit(); } 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; for (final String regex : REGEXES) { 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 @@ -989,18 +991,19 @@ public class YoutubeStreamExtractor extends StreamExtractor { if (formatData.has("url")) { streamUrl = formatData.getString("url"); } else { - // this url has an encrypted signature + // this url has an obfuscated signature final String cipherString = formatData.has("cipher") ? formatData.getString("cipher") : formatData.getString("signatureCipher"); final Map cipher = Parser.compatParseMap(cipherString); streamUrl = cipher.get("url") + "&" + cipher.get("sp") + "=" - + decryptSignature(cipher.get("s"), decryptionCode); + + deobfuscateSignature(cipher.get("s"), deobfuscationCode); } urlAndItags.put(streamUrl, itagItem); } - } catch (UnsupportedEncodingException ignored) {} + } catch (UnsupportedEncodingException ignored) { + } } } diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractorTest.java index 28c8d0b2..6e386b1d 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractorTest.java @@ -19,9 +19,9 @@ import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; public class SoundcloudStreamExtractorTest { - public static class LilUziVertDoWhatIWant extends DefaultStreamExtractorTest { - private static final String ID = "do-what-i-want-produced-by-maaly-raw-don-cannon"; - private static final String UPLOADER = "https://soundcloud.com/liluzivert"; + public static class CreativeCommonsPlaysWellWithOthers extends DefaultStreamExtractorTest { + 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/wearecc"; private static final int TIMESTAMP = 69; private static final String URL = UPLOADER + "/" + ID + "#t=" + TIMESTAMP; private static StreamExtractor extractor; @@ -35,20 +35,21 @@ public class SoundcloudStreamExtractorTest { @Override public StreamExtractor extractor() { return extractor; } @Override public StreamingService expectedService() { return SoundCloud; } - @Override public String expectedName() { return "Do What I Want [Produced By Maaly Raw + Don Cannon]"; } - @Override public String expectedId() { return "276206960"; } + @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 "597253485"; } @Override public String expectedUrlContains() { return UPLOADER + "/" + ID; } @Override public String expectedOriginalUrlContains() { return URL; } @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 List expectedDescriptionContains() { return Arrays.asList("The Perfect LUV TapeĀ®"); } - @Override public long expectedLength() { return 175; } + @Override public List expectedDescriptionContains() { return Arrays.asList("Stigmergy is a mechanism of indirect coordination", + "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 expectedViewCountAtLeast() { return 75413600; } - @Nullable @Override public String expectedUploadDate() { return "2016-07-31 18:18:07.000"; } - @Nullable @Override public String expectedTextualUploadDate() { return "2016-07-31 18:18:07"; } + @Override public long expectedViewCountAtLeast() { return 27000; } + @Nullable @Override public String expectedUploadDate() { return "2019-03-28 13:36:18.000"; } + @Nullable @Override public String expectedTextualUploadDate() { return "2019-03-28 13:36:18"; } @Override public long expectedLikeCountAtLeast() { return -1; } @Override public long expectedDislikeCountAtLeast() { return -1; } @Override public boolean expectedHasVideoStreams() { return false; } @@ -56,26 +57,4 @@ public class SoundcloudStreamExtractorTest { @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(); - } - } } diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java index a916c560..1c461fa8 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java @@ -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. - *

- * 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 { private static YoutubeChannelExtractor extractor; diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/search/YoutubeMusicSearchExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/search/YoutubeMusicSearchExtractorTest.java index eeac8c49..28c4519e 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/search/YoutubeMusicSearchExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/search/YoutubeMusicSearchExtractorTest.java @@ -153,8 +153,8 @@ public class YoutubeMusicSearchExtractorTest { public static class CorrectedSearch extends DefaultSearchExtractorTest { private static SearchExtractor extractor; - private static final String QUERY = "duo lipa"; - private static final String EXPECTED_SUGGESTION = "dua lipa"; + private static final String QUERY = "nocopyrigh sounds"; + private static final String EXPECTED_SUGGESTION = "nocopyrightsounds"; @BeforeClass public static void setUp() throws Exception { diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java index 6a3aa4f2..cc627f7f 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java @@ -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 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 { private static final String ID = "7PIMiDcwNvc"; private static final int TIMESTAMP = 17;