mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-25 01:20:34 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
8126fdcd15
@ -36,6 +36,7 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only
|
|||||||
* Show Next/Related videos
|
* Show Next/Related videos
|
||||||
* Search YouTube in a specific language
|
* Search YouTube in a specific language
|
||||||
* Orbot/Tor support (no streaming yet, experimental)
|
* Orbot/Tor support (no streaming yet, experimental)
|
||||||
|
* Watch age restricted material
|
||||||
|
|
||||||
### Coming Features
|
### Coming Features
|
||||||
|
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package org.schabi.newpipe.extractor.youtube;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.Downloader;
|
||||||
|
import org.schabi.newpipe.extractor.ExtractionException;
|
||||||
|
import org.schabi.newpipe.extractor.ParsingException;
|
||||||
|
import org.schabi.newpipe.extractor.VideoInfo;
|
||||||
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class YoutubeStreamExtractorRestrictedTest extends AndroidTestCase {
|
||||||
|
private YoutubeStreamExtractor extractor;
|
||||||
|
|
||||||
|
public void setUp() throws IOException, ExtractionException {
|
||||||
|
extractor = new YoutubeStreamExtractor("https://www.youtube.com/watch?v=i6JTvzrpBy0",
|
||||||
|
new Downloader());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetInvalidTimeStamp() throws ParsingException {
|
||||||
|
assertTrue(Integer.toString(extractor.getTimeStamp()),
|
||||||
|
extractor.getTimeStamp() <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetValidTimeStamp() throws ExtractionException, IOException {
|
||||||
|
YoutubeStreamExtractor extractor =
|
||||||
|
new YoutubeStreamExtractor("https://youtu.be/FmG385_uUys?t=174", new Downloader());
|
||||||
|
assertTrue(Integer.toString(extractor.getTimeStamp()),
|
||||||
|
extractor.getTimeStamp() == 174);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetAgeLimit() throws ParsingException {
|
||||||
|
assertTrue(extractor.getAgeLimit() == 18);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetTitle() throws ParsingException {
|
||||||
|
assertTrue(!extractor.getTitle().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetDescription() throws ParsingException {
|
||||||
|
assertTrue(extractor.getDescription() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetUploader() throws ParsingException {
|
||||||
|
assertTrue(!extractor.getUploader().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetLength() throws ParsingException {
|
||||||
|
assertTrue(extractor.getLength() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetViews() throws ParsingException {
|
||||||
|
assertTrue(extractor.getLength() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetUploadDate() throws ParsingException {
|
||||||
|
assertTrue(extractor.getUploadDate().length() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetThumbnailUrl() throws ParsingException {
|
||||||
|
assertTrue(extractor.getThumbnailUrl(),
|
||||||
|
extractor.getThumbnailUrl().contains("https://"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetUploaderThumbnailUrl() throws ParsingException {
|
||||||
|
assertTrue(extractor.getUploaderThumbnailUrl(),
|
||||||
|
extractor.getUploaderThumbnailUrl().contains("https://"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetAudioStreams() throws ParsingException {
|
||||||
|
assertTrue(!extractor.getAudioStreams().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetVideoStreams() throws ParsingException {
|
||||||
|
for(VideoInfo.VideoStream s : extractor.getVideoStreams()) {
|
||||||
|
assertTrue(s.url,
|
||||||
|
s.url.contains("https://"));
|
||||||
|
assertTrue(s.resolution.length() > 0);
|
||||||
|
assertTrue(Integer.toString(s.format),
|
||||||
|
0 <= s.format && s.format <= 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -181,9 +181,13 @@ public class VideoItemDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
//todo: fix expired thread error:
|
boolean show_age_restricted_content = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||||
// If the thread calling this runnable is expired, the following function will crash.
|
.getBoolean(activity.getString(R.string.show_age_restricted_content), false);
|
||||||
|
if(videoInfo.age_limit == 0 || show_age_restricted_content) {
|
||||||
updateInfo(videoInfo);
|
updateInfo(videoInfo);
|
||||||
|
} else {
|
||||||
|
onNotSpecifiedContentErrorWithMessage(R.string.video_is_age_restricted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,7 +422,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||||||
imageLoader.displayImage(info.uploader_thumbnail_url,
|
imageLoader.displayImage(info.uploader_thumbnail_url,
|
||||||
uploaderThumb, displayImageOptions, new ThumbnailLoadingListener());
|
uploaderThumb, displayImageOptions, new ThumbnailLoadingListener());
|
||||||
}
|
}
|
||||||
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty() && info.next_video != null) {
|
||||||
imageLoader.displayImage(info.next_video.thumbnail_url,
|
imageLoader.displayImage(info.next_video.thumbnail_url,
|
||||||
nextVideoThumb, displayImageOptions, new ThumbnailLoadingListener());
|
nextVideoThumb, displayImageOptions, new ThumbnailLoadingListener());
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.schabi.newpipe.extractor;
|
package org.schabi.newpipe.extractor;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -53,7 +55,11 @@ public class Parser {
|
|||||||
Map<String, String> map = new HashMap<>();
|
Map<String, String> map = new HashMap<>();
|
||||||
for(String arg : input.split("&")) {
|
for(String arg : input.split("&")) {
|
||||||
String[] split_arg = arg.split("=");
|
String[] split_arg = arg.split("=");
|
||||||
|
if(split_arg.length > 1) {
|
||||||
map.put(split_arg[0], URLDecoder.decode(split_arg[1], "UTF-8"));
|
map.put(split_arg[0], URLDecoder.decode(split_arg[1], "UTF-8"));
|
||||||
|
} else {
|
||||||
|
map.put(split_arg[0], "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,12 @@ public class VideoInfo extends AbstractVideoInfo {
|
|||||||
videoInfo.webpage_url = extractor.getPageUrl();
|
videoInfo.webpage_url = extractor.getPageUrl();
|
||||||
videoInfo.id = uiconv.getVideoId(extractor.getPageUrl());
|
videoInfo.id = uiconv.getVideoId(extractor.getPageUrl());
|
||||||
videoInfo.title = extractor.getTitle();
|
videoInfo.title = extractor.getTitle();
|
||||||
|
videoInfo.age_limit = extractor.getAgeLimit();
|
||||||
|
|
||||||
if((videoInfo.webpage_url == null || videoInfo.webpage_url.isEmpty())
|
if((videoInfo.webpage_url == null || videoInfo.webpage_url.isEmpty())
|
||||||
|| (videoInfo.id == null || videoInfo.id.isEmpty())
|
|| (videoInfo.id == null || videoInfo.id.isEmpty())
|
||||||
|| (videoInfo.title == null /* videoInfo.title can be empty of course */));
|
|| (videoInfo.title == null /* videoInfo.title can be empty of course */)
|
||||||
|
|| (videoInfo.age_limit == -1));
|
||||||
|
|
||||||
return videoInfo;
|
return videoInfo;
|
||||||
}
|
}
|
||||||
@ -192,6 +194,11 @@ public class VideoInfo extends AbstractVideoInfo {
|
|||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
videoInfo.addException(e);
|
videoInfo.addException(e);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
videoInfo.addException(e);
|
||||||
|
}
|
||||||
|
|
||||||
return videoInfo;
|
return videoInfo;
|
||||||
}
|
}
|
||||||
@ -209,7 +216,7 @@ public class VideoInfo extends AbstractVideoInfo {
|
|||||||
public String dashMpdUrl = "";
|
public String dashMpdUrl = "";
|
||||||
public int duration = -1;
|
public int duration = -1;
|
||||||
|
|
||||||
public int age_limit = 0;
|
public int age_limit = -1;
|
||||||
public int like_count = -1;
|
public int like_count = -1;
|
||||||
public int dislike_count = -1;
|
public int dislike_count = -1;
|
||||||
public String average_rating = "";
|
public String average_rating = "";
|
||||||
|
@ -24,6 +24,8 @@ import java.io.IOException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 06.08.15.
|
* Created by Christian Schabesberger on 06.08.15.
|
||||||
@ -168,7 +170,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
private static final String TAG = YoutubeStreamExtractor.class.toString();
|
private static final String TAG = YoutubeStreamExtractor.class.toString();
|
||||||
private final Document doc;
|
private final Document doc;
|
||||||
private JSONObject playerArgs;
|
private JSONObject playerArgs;
|
||||||
//private Map<String, String> videoInfoPage;
|
private boolean isAgeRestricted;
|
||||||
|
private Map<String, String> videoInfoPage;
|
||||||
|
|
||||||
// static values
|
// static values
|
||||||
private static final String DECRYPTION_FUNC_NAME="decrypt";
|
private static final String DECRYPTION_FUNC_NAME="decrypt";
|
||||||
@ -187,79 +190,123 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
this.pageUrl = pageUrl;
|
this.pageUrl = pageUrl;
|
||||||
String pageContent = downloader.download(urlidhandler.cleanUrl(pageUrl));
|
String pageContent = downloader.download(urlidhandler.cleanUrl(pageUrl));
|
||||||
doc = Jsoup.parse(pageContent, pageUrl);
|
doc = Jsoup.parse(pageContent, pageUrl);
|
||||||
String ytPlayerConfigRaw;
|
|
||||||
JSONObject ytPlayerConfig;
|
JSONObject ytPlayerConfig;
|
||||||
|
String playerUrl;
|
||||||
|
|
||||||
//attempt to load the youtube js player JSON arguments
|
// Check if the video is age restricted
|
||||||
boolean isLiveStream = false; //used to determine if this is a livestream or not
|
if (pageContent.contains("<meta property=\"og:restrictions:age")) {
|
||||||
try {
|
String videoInfoUrl = GET_VIDEO_INFO_URL.replace("%%video_id%%",
|
||||||
ytPlayerConfigRaw =
|
urlidhandler.getVideoId(pageUrl)).replace("$$el_type$$", "&" + EL_INFO);
|
||||||
Parser.matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", pageContent);
|
String videoInfoPageString = downloader.download(videoInfoUrl);
|
||||||
ytPlayerConfig = new JSONObject(ytPlayerConfigRaw);
|
videoInfoPage = Parser.compatParseMap(videoInfoPageString);
|
||||||
playerArgs = ytPlayerConfig.getJSONObject("args");
|
playerUrl = getPlayerUrlFromRestrictedVideo(pageUrl);
|
||||||
|
isAgeRestricted = true;
|
||||||
// check if we have a live stream. We need to filter it, since its not yet supported.
|
} else {
|
||||||
if((playerArgs.has("ps") && playerArgs.get("ps").toString().equals("live"))
|
ytPlayerConfig = getPlayerConfig(pageContent);
|
||||||
|| (playerArgs.get("url_encoded_fmt_stream_map").toString().isEmpty())) {
|
playerArgs = getPlayerArgs(ytPlayerConfig);
|
||||||
isLiveStream = true;
|
playerUrl = getPlayerUrl(ytPlayerConfig);
|
||||||
|
isAgeRestricted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(decryptionCode.isEmpty()) {
|
||||||
|
decryptionCode = loadDecryptionCode(playerUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject getPlayerConfig(String pageContent) throws ParsingException {
|
||||||
|
try {
|
||||||
|
String ytPlayerConfigRaw =
|
||||||
|
Parser.matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", pageContent);
|
||||||
|
return new JSONObject(ytPlayerConfigRaw);
|
||||||
} catch (Parser.RegexException e) {
|
} catch (Parser.RegexException e) {
|
||||||
String errorReason = findErrorReason(doc);
|
String errorReason = findErrorReason(doc);
|
||||||
switch(errorReason) {
|
switch(errorReason) {
|
||||||
case "GEMA":
|
case "GEMA":
|
||||||
throw new GemaException(errorReason);
|
throw new GemaException(errorReason);
|
||||||
case "":
|
case "":
|
||||||
throw new ParsingException("player config empty", e);
|
throw new ContentNotAvailableException("Content not available: player config empty", e);
|
||||||
default:
|
default:
|
||||||
throw new ContentNotAvailableException("Content not available", e);
|
throw new ContentNotAvailableException("Content not available", e);
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
throw new ParsingException("Could not parse yt player config", e);
|
throw new ParsingException("Could not parse yt player config", e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject getPlayerArgs(JSONObject playerConfig) throws ParsingException {
|
||||||
|
JSONObject playerArgs;
|
||||||
|
|
||||||
|
//attempt to load the youtube js player JSON arguments
|
||||||
|
boolean isLiveStream = false; //used to determine if this is a livestream or not
|
||||||
|
try {
|
||||||
|
playerArgs = playerConfig.getJSONObject("args");
|
||||||
|
|
||||||
|
// check if we have a live stream. We need to filter it, since its not yet supported.
|
||||||
|
if((playerArgs.has("ps") && playerArgs.get("ps").toString().equals("live"))
|
||||||
|
|| (playerArgs.get("url_encoded_fmt_stream_map").toString().isEmpty())) {
|
||||||
|
isLiveStream = true;
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new ParsingException("Could not parse yt player config", e);
|
||||||
|
}
|
||||||
if (isLiveStream) {
|
if (isLiveStream) {
|
||||||
throw new LiveStreamException();
|
throw new LiveStreamException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return playerArgs;
|
||||||
/* not yet nececeary
|
|
||||||
|
|
||||||
|
|
||||||
// get videoInfo page
|
|
||||||
try {
|
|
||||||
//Parser.unescapeEntities(url_data_str, true).split("&")
|
|
||||||
String getVideoInfoUrl = GET_VIDEO_INFO_URL.replace("%%video_id%%",
|
|
||||||
urlidhandler.getVideoId(pageUrl)).replace("$$el_type$$", "&" + EL_INFO);
|
|
||||||
videoInfoPage = Parser.compatParseMap(downloader.download(getVideoInfoUrl));
|
|
||||||
} catch(Exception e) {
|
|
||||||
throw new ParsingException("Could not load video info page.", e);
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
//----------------------------------
|
private String getPlayerUrl(JSONObject playerConfig) throws ParsingException {
|
||||||
// load and parse description code, if it isn't already initialised
|
|
||||||
//----------------------------------
|
|
||||||
if (decryptionCode.isEmpty()) {
|
|
||||||
try {
|
try {
|
||||||
// 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 decrypting cryptic signatures inside certain stream urls.
|
||||||
JSONObject ytAssets = ytPlayerConfig.getJSONObject("assets");
|
String playerUrl = "";
|
||||||
String playerUrl = ytAssets.getString("js");
|
|
||||||
|
JSONObject ytAssets = playerConfig.getJSONObject("assets");
|
||||||
|
playerUrl = ytAssets.getString("js");
|
||||||
|
|
||||||
if (playerUrl.startsWith("//")) {
|
if (playerUrl.startsWith("//")) {
|
||||||
playerUrl = "https:" + playerUrl;
|
playerUrl = "https:" + playerUrl;
|
||||||
}
|
}
|
||||||
decryptionCode = loadDecryptionCode(playerUrl);
|
return playerUrl;
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
throw new ParsingException(
|
throw new ParsingException(
|
||||||
"Could not load decryption code for the Youtube service.", e);
|
"Could not load decryption code for the Youtube service.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getPlayerUrlFromRestrictedVideo(String pageUrl) throws ParsingException {
|
||||||
|
try {
|
||||||
|
String playerUrl = "";
|
||||||
|
String videoId = urlidhandler.getVideoId(pageUrl);
|
||||||
|
String embedUrl = "https://www.youtube.com/embed/" + videoId;
|
||||||
|
String embedPageContent = downloader.download(embedUrl);
|
||||||
|
//todo: find out if this can be reapaced by Parser.matchGroup1()
|
||||||
|
Pattern assetsPattern = Pattern.compile("\"assets\":.+?\"js\":\\s*(\"[^\"]+\")");
|
||||||
|
Matcher patternMatcher = assetsPattern.matcher(embedPageContent);
|
||||||
|
while (patternMatcher.find()) {
|
||||||
|
playerUrl = patternMatcher.group(1);
|
||||||
|
}
|
||||||
|
playerUrl = playerUrl.replace("\\", "").replace("\"", "");
|
||||||
|
|
||||||
|
if (playerUrl.startsWith("//")) {
|
||||||
|
playerUrl = "https:" + playerUrl;
|
||||||
|
}
|
||||||
|
return playerUrl;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ParsingException(
|
||||||
|
"Could load decryption code form restricted video for the Youtube service.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTitle() throws ParsingException {
|
public String getTitle() throws ParsingException {
|
||||||
try {//json player args method
|
try {
|
||||||
|
if (playerArgs == null) {
|
||||||
|
return videoInfoPage.get("title");
|
||||||
|
}
|
||||||
|
//json player args method
|
||||||
return playerArgs.getString("title");
|
return playerArgs.getString("title");
|
||||||
} catch(JSONException je) {//html <meta> method
|
} catch(JSONException je) {//html <meta> method
|
||||||
je.printStackTrace();
|
je.printStackTrace();
|
||||||
@ -283,7 +330,11 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUploader() throws ParsingException {
|
public String getUploader() throws ParsingException {
|
||||||
try {//json player args method
|
try {
|
||||||
|
if (playerArgs == null) {
|
||||||
|
return videoInfoPage.get("author");
|
||||||
|
}
|
||||||
|
//json player args method
|
||||||
return playerArgs.getString("author");
|
return playerArgs.getString("author");
|
||||||
} catch(JSONException je) {
|
} catch(JSONException je) {
|
||||||
je.printStackTrace();
|
je.printStackTrace();
|
||||||
@ -299,6 +350,9 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
@Override
|
@Override
|
||||||
public int getLength() throws ParsingException {
|
public int getLength() throws ParsingException {
|
||||||
try {
|
try {
|
||||||
|
if (playerArgs == null) {
|
||||||
|
return Integer.valueOf(videoInfoPage.get("length_seconds"));
|
||||||
|
}
|
||||||
return playerArgs.getInt("length_seconds");
|
return playerArgs.getInt("length_seconds");
|
||||||
} catch (JSONException e) {//todo: find fallback method
|
} catch (JSONException e) {//todo: find fallback method
|
||||||
throw new ParsingException("failed to load video duration from JSON args", e);
|
throw new ParsingException("failed to load video duration from JSON args", e);
|
||||||
@ -339,6 +393,9 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
} catch (JSONException je) {
|
} catch (JSONException je) {
|
||||||
throw new ParsingException(
|
throw new ParsingException(
|
||||||
"failed to extract thumbnail URL from JSON args; trying to extract it from HTML", je);
|
"failed to extract thumbnail URL from JSON args; trying to extract it from HTML", je);
|
||||||
|
} catch (NullPointerException ne) {
|
||||||
|
// Get from the video info page instead
|
||||||
|
return videoInfoPage.get("thumbnail_url");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,7 +436,13 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
public List<VideoInfo.AudioStream> getAudioStreams() throws ParsingException {
|
public List<VideoInfo.AudioStream> getAudioStreams() throws ParsingException {
|
||||||
Vector<VideoInfo.AudioStream> audioStreams = new Vector<>();
|
Vector<VideoInfo.AudioStream> audioStreams = new Vector<>();
|
||||||
try{
|
try{
|
||||||
String encoded_url_map = playerArgs.getString("adaptive_fmts");
|
String encoded_url_map;
|
||||||
|
// playerArgs could be null if the video is age restricted
|
||||||
|
if (playerArgs == null) {
|
||||||
|
encoded_url_map = videoInfoPage.get("adaptive_fmts");
|
||||||
|
} else {
|
||||||
|
encoded_url_map = playerArgs.getString("adaptive_fmts");
|
||||||
|
}
|
||||||
for(String url_data_str : encoded_url_map.split(",")) {
|
for(String url_data_str : encoded_url_map.split(",")) {
|
||||||
// This loop iterates through multiple streams, therefor tags
|
// This loop iterates through multiple streams, therefor tags
|
||||||
// is related to one and the same stream at a time.
|
// is related to one and the same stream at a time.
|
||||||
@ -416,7 +479,13 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
Vector<VideoInfo.VideoStream> videoStreams = new Vector<>();
|
Vector<VideoInfo.VideoStream> videoStreams = new Vector<>();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
String encoded_url_map = playerArgs.getString("url_encoded_fmt_stream_map");
|
String encoded_url_map;
|
||||||
|
// playerArgs could be null if the video is age restricted
|
||||||
|
if (playerArgs == null) {
|
||||||
|
encoded_url_map = videoInfoPage.get("url_encoded_fmt_stream_map");
|
||||||
|
} else {
|
||||||
|
encoded_url_map = playerArgs.getString("url_encoded_fmt_stream_map");
|
||||||
|
}
|
||||||
for(String url_data_str : encoded_url_map.split(",")) {
|
for(String url_data_str : encoded_url_map.split(",")) {
|
||||||
try {
|
try {
|
||||||
// This loop iterates through multiple streams, therefor tags
|
// This loop iterates through multiple streams, therefor tags
|
||||||
@ -499,7 +568,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
int minutes = (minutesString.isEmpty() ? 0 : Integer.parseInt(minutesString));
|
int minutes = (minutesString.isEmpty() ? 0 : Integer.parseInt(minutesString));
|
||||||
int hours = (hoursString.isEmpty() ? 0 : Integer.parseInt(hoursString));
|
int hours = (hoursString.isEmpty() ? 0 : Integer.parseInt(hoursString));
|
||||||
|
|
||||||
int ret = seconds + (60 * minutes) + (3600 * hours);//don't trust BODMAS!
|
//don't trust BODMAS!
|
||||||
|
int ret = seconds + (60 * minutes) + (3600 * hours);
|
||||||
//Log.d(TAG, "derived timestamp value:"+ret);
|
//Log.d(TAG, "derived timestamp value:"+ret);
|
||||||
return ret;
|
return ret;
|
||||||
//the ordering varies internationally
|
//the ordering varies internationally
|
||||||
@ -513,15 +583,24 @@ public class YoutubeStreamExtractor implements StreamExtractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAgeLimit() throws ParsingException {
|
public int getAgeLimit() throws ParsingException {
|
||||||
// Not yet implemented.
|
if (!isAgeRestricted) {
|
||||||
// Also you need to be logged in to see age restricted videos on youtube,
|
|
||||||
// therefore NP is not able to receive such videos.
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
return Integer.valueOf(doc.head()
|
||||||
|
.getElementsByAttributeValue("property", "og:restrictions:age")
|
||||||
|
.attr("content").replace("+", ""));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException("Could not get age restriction");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAverageRating() throws ParsingException {
|
public String getAverageRating() throws ParsingException {
|
||||||
try {
|
try {
|
||||||
|
if (playerArgs == null) {
|
||||||
|
videoInfoPage.get("avg_rating");
|
||||||
|
}
|
||||||
return playerArgs.getString("avg_rating");
|
return playerArgs.getString("avg_rating");
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
throw new ParsingException("Could not get Average rating", e);
|
throw new ParsingException("Could not get Average rating", e);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<!-- Categories -->
|
<!-- Categories -->
|
||||||
<string name="settings_category_video_audio">settings_category_video_audio</string>
|
<string name="settings_category_video_audio">settings_category_video_audio</string>
|
||||||
<string name="settings_category_appearance">settings_category_appearance</string>
|
<string name="settings_category_appearance">settings_category_appearance</string>
|
||||||
|
<string name="settings_content_options">settings_content_options</string>
|
||||||
<string name="settings_category_other">settings_category_other</string>
|
<string name="settings_category_other">settings_category_other</string>
|
||||||
<!-- Key values -->
|
<!-- Key values -->
|
||||||
<string name="download_path_key">download_path</string>
|
<string name="download_path_key">download_path</string>
|
||||||
@ -207,5 +208,6 @@
|
|||||||
<item>日本語</item>
|
<item>日本語</item>
|
||||||
<item>한국어</item>
|
<item>한국어</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string name="show_age_restricted_content">show_age_restricted_content</string>
|
||||||
<string name="use_tor_key">use_tor</string>
|
<string name="use_tor_key">use_tor</string>
|
||||||
</resources>
|
</resources>
|
@ -74,6 +74,9 @@
|
|||||||
<string name="background_player_playing_toast">Playing in background</string>
|
<string name="background_player_playing_toast">Playing in background</string>
|
||||||
<string name="c3s_url" translatable="false">https://www.c3s.cc/</string>
|
<string name="c3s_url" translatable="false">https://www.c3s.cc/</string>
|
||||||
<string name="play_btn_text">Play</string>
|
<string name="play_btn_text">Play</string>
|
||||||
|
<string name="content">Content</string>
|
||||||
|
<string name="show_age_restricted_content_title">Show age restricted content</string>
|
||||||
|
<string name="video_is_age_restricted">Video is Age restricted. Enable age restricted videos in the settings first.</string>
|
||||||
|
|
||||||
<!-- error strings -->
|
<!-- error strings -->
|
||||||
<string name="general_error">Error</string>
|
<string name="general_error">Error</string>
|
||||||
|
@ -63,8 +63,8 @@
|
|||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="@string/settings_category_other"
|
android:key="@string/settings_content_options"
|
||||||
android:title="@string/settings_category_other_title"
|
android:title="@string/content"
|
||||||
android:textAllCaps="true">
|
android:textAllCaps="true">
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
@ -75,6 +75,18 @@
|
|||||||
android:entryValues="@array/language_codes"
|
android:entryValues="@array/language_codes"
|
||||||
android:defaultValue="@string/default_language_value" />
|
android:defaultValue="@string/default_language_value" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="@string/show_age_restricted_content"
|
||||||
|
android:title="@string/show_age_restricted_content_title"
|
||||||
|
android:defaultValue="false"/>
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="@string/settings_category_other"
|
||||||
|
android:title="@string/settings_category_other_title"
|
||||||
|
android:textAllCaps="true">
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:key="@string/download_path_key"
|
android:key="@string/download_path_key"
|
||||||
android:title="@string/download_path_title"
|
android:title="@string/download_path_title"
|
||||||
|
Loading…
Reference in New Issue
Block a user