mirror of
https://github.com/TeamNewPipe/NewPipeExtractor
synced 2025-10-06 00:23:15 +02:00
Merge pull request #1323 from absurdlylongusername/minor-code-cleanup
This commit is contained in:
@@ -69,4 +69,12 @@ public class DateWrapper implements Serializable {
|
||||
public boolean isApproximation() {
|
||||
return isApproximation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DateWrapper{"
|
||||
+ "offsetDateTime=" + offsetDateTime
|
||||
+ ", isApproximation=" + isApproximation
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
@@ -121,7 +121,8 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
||||
|
||||
@Override
|
||||
public long getTimeStamp() throws ParsingException {
|
||||
return getTimestampSeconds("(#t=\\d{0,3}h?\\d{0,3}m?\\d{1,3}s?)");
|
||||
final var timestamp = getTimestampSeconds("(#t=\\d{0,3}h?\\d{0,3}m?\\d{1,3}s?)");
|
||||
return timestamp == -2 ? 0 : timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,7 +171,7 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
||||
|
||||
try {
|
||||
final JsonArray transcodings = track.getObject("media")
|
||||
.getArray("transcodings");
|
||||
.getArray("transcodings");
|
||||
if (!isNullOrEmpty(transcodings)) {
|
||||
// Get information about what stream formats are available
|
||||
extractAudioStreams(transcodings, audioStreams);
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package org.schabi.newpipe.extractor.services.soundcloud.linkHandler;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper;
|
||||
@@ -9,11 +11,18 @@ import org.schabi.newpipe.extractor.utils.Utils;
|
||||
public final class SoundcloudStreamLinkHandlerFactory extends LinkHandlerFactory {
|
||||
private static final SoundcloudStreamLinkHandlerFactory INSTANCE
|
||||
= new SoundcloudStreamLinkHandlerFactory();
|
||||
private static final String URL_PATTERN = "^https?://(www\\.|m\\.|on\\.)?"
|
||||
+ "soundcloud.com/[0-9a-z_-]+"
|
||||
+ "/(?!(tracks|albums|sets|reposts|followers|following)/?$)[0-9a-z_-]+/?([#?].*)?$";
|
||||
private static final String API_URL_PATTERN = "^https?://api-v2\\.soundcloud.com"
|
||||
+ "/(tracks|albums|sets|reposts|followers|following)/([0-9a-z_-]+)/";
|
||||
|
||||
private static final Pattern URL_PATTERN = Pattern.compile(
|
||||
"^https?://(?:www\\.|m\\.|on\\.)?"
|
||||
+ "soundcloud.com/[0-9a-z_-]+"
|
||||
+ "/(?!(?:tracks|albums|sets|reposts|followers|following)/?$)[0-9a-z_-]+/?(?:[#?].*)?$"
|
||||
);
|
||||
|
||||
private static final Pattern API_URL_PATTERN = Pattern.compile(
|
||||
"^https?://api-v2\\.soundcloud.com"
|
||||
+ "/(tracks|albums|sets|reposts|followers|following)/([0-9a-z_-]+)/"
|
||||
);
|
||||
|
||||
private SoundcloudStreamLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
|
@@ -88,7 +88,8 @@ public final class AudioStream extends Stream {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the identifier of the {@link AudioStream}.
|
||||
* Set the identifier of the {@link AudioStream} which uniquely identifies the stream,
|
||||
* e.g. for YouTube this would be the itag
|
||||
*
|
||||
* <p>
|
||||
* It <b>must not be null</b> and should be non empty.
|
||||
@@ -108,14 +109,14 @@ public final class AudioStream extends Stream {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content of the {@link AudioStream}.
|
||||
*
|
||||
* Set the content or the URL of the {@link AudioStream}, depending on whether isUrl is
|
||||
* true
|
||||
* <p>
|
||||
* It must not be null, and should be non empty.
|
||||
* </p>
|
||||
*
|
||||
* @param content the content of the {@link AudioStream}
|
||||
* @param isUrl whether the content is a URL
|
||||
* @param isUrl whether content is the URL or the actual content of e.g. a DASH manifest
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder setContent(@Nonnull final String content,
|
||||
@@ -126,7 +127,7 @@ public final class AudioStream extends Stream {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MediaFormat} used by the {@link AudioStream}.
|
||||
* Set the {@link MediaFormat} used by the {@link AudioStream}, which can be null
|
||||
*
|
||||
* <p>
|
||||
* It should be one of the audio {@link MediaFormat}s ({@link MediaFormat#M4A M4A},
|
||||
@@ -278,16 +279,22 @@ public final class AudioStream extends Stream {
|
||||
* Build an {@link AudioStream} using the builder's current values.
|
||||
*
|
||||
* <p>
|
||||
* The identifier and the content (and so the {@code isUrl} boolean) properties must have
|
||||
* The identifier and the content (and thus {@code isUrl}) properties must have
|
||||
* been set.
|
||||
* </p>
|
||||
*
|
||||
* @return a new {@link AudioStream} using the builder's current values
|
||||
* @throws IllegalStateException if {@code id}, {@code content} (and so {@code isUrl}) or
|
||||
* @throws IllegalStateException if {@code id}, {@code content} (and thus {@code isUrl}) or
|
||||
* {@code deliveryMethod} have been not set, or have been set as {@code null}
|
||||
*/
|
||||
@Nonnull
|
||||
public AudioStream build() {
|
||||
validateBuild();
|
||||
|
||||
return new AudioStream(this);
|
||||
}
|
||||
|
||||
void validateBuild() {
|
||||
if (id == null) {
|
||||
throw new IllegalStateException(
|
||||
"The identifier of the audio stream has been not set or is null. If you "
|
||||
@@ -305,64 +312,39 @@ public final class AudioStream extends Stream {
|
||||
"The delivery method of the audio stream has been set as null, which is "
|
||||
+ "not allowed. Pass a valid one instead with setDeliveryMethod.");
|
||||
}
|
||||
|
||||
return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate,
|
||||
manifestUrl, audioTrackId, audioTrackName, audioLocale, audioTrackType,
|
||||
itagItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new audio stream.
|
||||
* Create a new audio stream using the given {@link Builder}.
|
||||
*
|
||||
* @param id the identifier which uniquely identifies the stream, e.g. for YouTube
|
||||
* this would be the itag
|
||||
* @param content the content or the URL of the stream, depending on whether isUrl is
|
||||
* true
|
||||
* @param isUrl whether content is the URL or the actual content of e.g. a DASH
|
||||
* manifest
|
||||
* @param format the {@link MediaFormat} used by the stream, which can be null
|
||||
* @param deliveryMethod the {@link DeliveryMethod} of the stream
|
||||
* @param averageBitrate the average bitrate of the stream (which can be unknown, see
|
||||
* {@link #UNKNOWN_BITRATE})
|
||||
* @param audioTrackId the id of the audio track
|
||||
* @param audioTrackName the name of the audio track
|
||||
* @param audioLocale the {@link Locale} of the audio stream, representing its language
|
||||
* @param itagItem the {@link ItagItem} corresponding to the stream, which cannot be null
|
||||
* @param manifestUrl the URL of the manifest this stream comes from (if applicable,
|
||||
* otherwise null)
|
||||
* @param builder The {@link Builder} to use to create the audio stream
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:ParameterNumber")
|
||||
private AudioStream(@Nonnull final String id,
|
||||
@Nonnull final String content,
|
||||
final boolean isUrl,
|
||||
@Nullable final MediaFormat format,
|
||||
@Nonnull final DeliveryMethod deliveryMethod,
|
||||
final int averageBitrate,
|
||||
@Nullable final String manifestUrl,
|
||||
@Nullable final String audioTrackId,
|
||||
@Nullable final String audioTrackName,
|
||||
@Nullable final Locale audioLocale,
|
||||
@Nullable final AudioTrackType audioTrackType,
|
||||
@Nullable final ItagItem itagItem) {
|
||||
super(id, content, isUrl, format, deliveryMethod, manifestUrl);
|
||||
if (itagItem != null) {
|
||||
this.itagItem = itagItem;
|
||||
this.itag = itagItem.id;
|
||||
this.quality = itagItem.getQuality();
|
||||
this.bitrate = itagItem.getBitrate();
|
||||
this.initStart = itagItem.getInitStart();
|
||||
this.initEnd = itagItem.getInitEnd();
|
||||
this.indexStart = itagItem.getIndexStart();
|
||||
this.indexEnd = itagItem.getIndexEnd();
|
||||
this.codec = itagItem.getCodec();
|
||||
AudioStream(final Builder builder) {
|
||||
super(builder.id,
|
||||
builder.content,
|
||||
builder.isUrl,
|
||||
builder.mediaFormat,
|
||||
builder.deliveryMethod,
|
||||
builder.manifestUrl);
|
||||
if (builder.itagItem != null) {
|
||||
this.itagItem = builder.itagItem;
|
||||
this.itag = builder.itagItem.id;
|
||||
this.quality = builder.itagItem.getQuality();
|
||||
this.bitrate = builder.itagItem.getBitrate();
|
||||
this.initStart = builder.itagItem.getInitStart();
|
||||
this.initEnd = builder.itagItem.getInitEnd();
|
||||
this.indexStart = builder.itagItem.getIndexStart();
|
||||
this.indexEnd = builder.itagItem.getIndexEnd();
|
||||
this.codec = builder.itagItem.getCodec();
|
||||
}
|
||||
this.averageBitrate = averageBitrate;
|
||||
this.audioTrackId = audioTrackId;
|
||||
this.audioTrackName = audioTrackName;
|
||||
this.audioLocale = audioLocale;
|
||||
this.audioTrackType = audioTrackType;
|
||||
this.averageBitrate = builder.averageBitrate;
|
||||
this.audioTrackId = builder.audioTrackId;
|
||||
this.audioTrackName = builder.audioTrackName;
|
||||
this.audioLocale = builder.audioLocale;
|
||||
this.audioTrackType = builder.audioTrackType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -17,11 +17,7 @@ public class Description implements Serializable {
|
||||
|
||||
public Description(@Nullable final String content, final int type) {
|
||||
this.type = type;
|
||||
if (content == null) {
|
||||
this.content = "";
|
||||
} else {
|
||||
this.content = content;
|
||||
}
|
||||
this.content = Objects.requireNonNullElse(content, "");
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
|
@@ -68,7 +68,7 @@ public abstract class Stream implements Serializable {
|
||||
* @param streamList the list of {@link Stream}s which will be compared
|
||||
* @return whether the list already contains one stream with equals stats
|
||||
*/
|
||||
public static boolean containSimilarStream(final Stream stream,
|
||||
public static boolean containSimilarStream(@Nonnull final Stream stream,
|
||||
final List<? extends Stream> streamList) {
|
||||
if (isNullOrEmpty(streamList)) {
|
||||
return false;
|
||||
@@ -98,11 +98,9 @@ public abstract class Stream implements Serializable {
|
||||
* @return whether the stream have the same stats or not, based on the criteria above
|
||||
*/
|
||||
public boolean equalStats(@Nullable final Stream other) {
|
||||
if (other == null || mediaFormat == null || other.mediaFormat == null) {
|
||||
return false;
|
||||
}
|
||||
return mediaFormat.id == other.mediaFormat.id && deliveryMethod == other.deliveryMethod
|
||||
&& isUrl == other.isUrl;
|
||||
return other != null && mediaFormat != null && other.mediaFormat != null
|
||||
&& mediaFormat.id == other.mediaFormat.id && deliveryMethod == other.deliveryMethod
|
||||
&& isUrl == other.isUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,6 +135,7 @@ public abstract class Stream implements Serializable {
|
||||
*
|
||||
* @return the content or URL
|
||||
*/
|
||||
@Nonnull
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
@@ -352,7 +352,7 @@ public class StreamInfo extends Info {
|
||||
private String uploaderUrl = "";
|
||||
@Nonnull
|
||||
private List<Image> uploaderAvatars = List.of();
|
||||
private boolean uploaderVerified = false;
|
||||
private boolean uploaderVerified;
|
||||
private long uploaderSubscriberCount = -1;
|
||||
|
||||
private String subChannelName = "";
|
||||
@@ -368,7 +368,7 @@ public class StreamInfo extends Info {
|
||||
private String hlsUrl = "";
|
||||
private List<InfoItem> relatedItems = List.of();
|
||||
|
||||
private long startPosition = 0;
|
||||
private long startPosition;
|
||||
private List<SubtitlesStream> subtitles = List.of();
|
||||
|
||||
private String host = "";
|
||||
@@ -376,11 +376,11 @@ public class StreamInfo extends Info {
|
||||
private String category = "";
|
||||
private String licence = "";
|
||||
private String supportInfo = "";
|
||||
private Locale language = null;
|
||||
private Locale language;
|
||||
private List<String> tags = List.of();
|
||||
private List<StreamSegment> streamSegments = List.of();
|
||||
private List<MetaInfo> metaInfo = List.of();
|
||||
private boolean shortFormContent = false;
|
||||
private boolean shortFormContent;
|
||||
|
||||
/**
|
||||
* Preview frames, e.g. for the storyboard / seekbar thumbnail preview
|
||||
|
@@ -14,7 +14,7 @@ import java.util.Objects;
|
||||
* <p>
|
||||
* This class is used to construct {@link org.schabi.newpipe.extractor.Image Image}
|
||||
* instances from a single base URL/path, in order to get all or most image resolutions provided,
|
||||
* depending of the service and the resolutions provided.
|
||||
* depending on the service and the resolutions provided.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
|
@@ -44,50 +44,113 @@ public final class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static Matcher matchOrThrow(@Nonnull final Pattern pattern,
|
||||
final String input) throws RegexException {
|
||||
final Matcher matcher = pattern.matcher(input);
|
||||
if (matcher.find()) {
|
||||
return matcher;
|
||||
} else {
|
||||
String errorMessage = "Failed to find pattern \"" + pattern.pattern() + "\"";
|
||||
if (input.length() <= 1024) {
|
||||
errorMessage += " inside of \"" + input + "\"";
|
||||
}
|
||||
throw new RegexException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches group 1 of the given pattern against the input
|
||||
* and returns the matched group
|
||||
*
|
||||
* @param pattern The regex pattern to match.
|
||||
* @param input The input string to match against.
|
||||
* @return The matching group as a string.
|
||||
* @throws RegexException If the pattern does not match the input or if the group is not found.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String matchGroup1(final String pattern, final String input)
|
||||
throws RegexException {
|
||||
return matchGroup(pattern, input, 1);
|
||||
}
|
||||
|
||||
public static String matchGroup1(final Pattern pattern,
|
||||
final String input) throws RegexException {
|
||||
/**
|
||||
* Matches group 1 of the given pattern against the input
|
||||
* and returns the matched group
|
||||
*
|
||||
* @param pattern The regex pattern to match.
|
||||
* @param input The input string to match against.
|
||||
* @return The matching group as a string.
|
||||
* @throws RegexException If the pattern does not match the input or if the group is not found.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String matchGroup1(final Pattern pattern, final String input)
|
||||
throws RegexException {
|
||||
return matchGroup(pattern, input, 1);
|
||||
}
|
||||
|
||||
public static String matchGroup(final String pattern,
|
||||
final String input,
|
||||
final int group) throws RegexException {
|
||||
/**
|
||||
* Matches the specified group of the given pattern against the input,
|
||||
* and returns the matched group
|
||||
*
|
||||
* @param pattern The regex pattern to match.
|
||||
* @param input The input string to match against.
|
||||
* @param group The group number to retrieve (1-based index).
|
||||
* @return The matching group as a string.
|
||||
* @throws RegexException If the pattern does not match the input or if the group is not found.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String matchGroup(final String pattern, final String input, final int group)
|
||||
throws RegexException {
|
||||
return matchGroup(Pattern.compile(pattern), input, group);
|
||||
}
|
||||
|
||||
public static String matchGroup(@Nonnull final Pattern pat,
|
||||
/**
|
||||
* Matches the specified group of the given pattern against the input,
|
||||
* and returns the matched group
|
||||
*
|
||||
* @param pattern The regex pattern to match.
|
||||
* @param input The input string to match against.
|
||||
* @param group The group number to retrieve (1-based index).
|
||||
* @return The matching group as a string.
|
||||
* @throws RegexException If the pattern does not match the input or if the group is not found.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String matchGroup(@Nonnull final Pattern pattern,
|
||||
final String input,
|
||||
final int group) throws RegexException {
|
||||
final Matcher matcher = pat.matcher(input);
|
||||
final boolean foundMatch = matcher.find();
|
||||
if (foundMatch) {
|
||||
return matcher.group(group);
|
||||
} else {
|
||||
// only pass input to exception message when it is not too long
|
||||
if (input.length() > 1024) {
|
||||
throw new RegexException("Failed to find pattern \"" + pat.pattern() + "\"");
|
||||
} else {
|
||||
throw new RegexException("Failed to find pattern \"" + pat.pattern()
|
||||
+ "\" inside of \"" + input + "\"");
|
||||
}
|
||||
}
|
||||
final int group)
|
||||
throws RegexException {
|
||||
return matchOrThrow(pattern, input).group(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches multiple patterns against the input string and
|
||||
* returns the first successful matcher
|
||||
*
|
||||
* @param patterns The array of regex patterns to match.
|
||||
* @param input The input string to match against.
|
||||
* @return A {@code Matcher} for the first successful match.
|
||||
* @throws RegexException If no patterns match the input or if {@code patterns} is empty.
|
||||
*/
|
||||
public static String matchGroup1MultiplePatterns(final Pattern[] patterns, final String input)
|
||||
throws RegexException {
|
||||
return matchMultiplePatterns(patterns, input).group(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches multiple patterns against the input string and
|
||||
* returns the first successful matcher
|
||||
*
|
||||
* @param patterns The array of regex patterns to match.
|
||||
* @param input The input string to match against.
|
||||
* @return A {@code Matcher} for the first successful match.
|
||||
* @throws RegexException If no patterns match the input or if {@code patterns} is empty.
|
||||
*/
|
||||
public static Matcher matchMultiplePatterns(final Pattern[] patterns, final String input)
|
||||
throws RegexException {
|
||||
Parser.RegexException exception = null;
|
||||
for (final Pattern pattern : patterns) {
|
||||
final Matcher matcher = pattern.matcher(input);
|
||||
RegexException exception = null;
|
||||
for (final var pattern : patterns) {
|
||||
final var matcher = pattern.matcher(input);
|
||||
if (matcher.find()) {
|
||||
return matcher;
|
||||
} else if (exception == null) {
|
||||
@@ -107,14 +170,11 @@ public final class Parser {
|
||||
}
|
||||
|
||||
public static boolean isMatch(final String pattern, final String input) {
|
||||
final Pattern pat = Pattern.compile(pattern);
|
||||
final Matcher mat = pat.matcher(input);
|
||||
return mat.find();
|
||||
return isMatch(Pattern.compile(pattern), input);
|
||||
}
|
||||
|
||||
public static boolean isMatch(@Nonnull final Pattern pattern, final String input) {
|
||||
final Matcher mat = pattern.matcher(input);
|
||||
return mat.find();
|
||||
return pattern.matcher(input).find();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@@ -110,12 +110,22 @@ public final class Utils {
|
||||
* @param url the url to be tested
|
||||
*/
|
||||
public static void checkUrl(final String pattern, final String url) throws ParsingException {
|
||||
checkUrl(Pattern.compile(pattern), url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the url matches the pattern.
|
||||
*
|
||||
* @param pattern the pattern that will be used to check the url
|
||||
* @param url the url to be tested
|
||||
*/
|
||||
public static void checkUrl(final Pattern pattern, final String url) throws ParsingException {
|
||||
if (isNullOrEmpty(url)) {
|
||||
throw new IllegalArgumentException("Url can't be null or empty");
|
||||
}
|
||||
|
||||
if (!Parser.isMatch(pattern, url.toLowerCase())) {
|
||||
throw new ParsingException("Url don't match the pattern");
|
||||
throw new ParsingException("Url doesn't match the pattern");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,6 @@ import org.schabi.newpipe.extractor.downloader.Response;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -35,7 +34,7 @@ public final class DownloaderTestImpl extends Downloader {
|
||||
// Required for certain services
|
||||
// For example Bandcamp otherwise fails on Windows with Java 17+
|
||||
// as their Fastly-CDN returns 403
|
||||
.connectionSpecs(Arrays.asList(ConnectionSpec.RESTRICTED_TLS))
|
||||
.connectionSpecs(List.of(ConnectionSpec.RESTRICTED_TLS))
|
||||
.build());
|
||||
}
|
||||
|
||||
|
@@ -132,7 +132,6 @@ public class SoundcloudStreamExtractorTest {
|
||||
@Override public long expectedDislikeCountAtLeast() { return -1; }
|
||||
@Override public boolean expectedHasAudioStreams() { return false; }
|
||||
@Override public boolean expectedHasVideoStreams() { return false; }
|
||||
@Override public boolean expectedHasRelatedItems() { return true; }
|
||||
@Override public boolean expectedHasSubtitles() { return false; }
|
||||
@Override public boolean expectedHasFrames() { return false; }
|
||||
@Override public int expectedStreamSegmentsCount() { return 0; }
|
||||
|
@@ -43,7 +43,7 @@ public class YoutubeSearchQHTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithContentfilter() throws Exception {
|
||||
public void testWithContentFilter() throws Exception {
|
||||
assertEquals("https://www.youtube.com/results?search_query=asdf&sp=EgIQAfABAQ%253D%253D", YouTube.getSearchQHFactory()
|
||||
.fromQuery("asdf", List.of(VIDEOS), "").getUrl());
|
||||
assertEquals("https://www.youtube.com/results?search_query=asdf&sp=EgIQAvABAQ%253D%253D", YouTube.getSearchQHFactory()
|
||||
|
Reference in New Issue
Block a user