[Soundcloud] Remove DRM-protected and downloadable formats extraction

DRM-protected streams have been added to some tracks, mostly from
major music companies. We do not support DRM streams in the extractor,
so they shouldn't be extracted and so waste time, energy and resources.

Extracting downloadable format requires login for a pretty long time,
so there is no point again to do requests to extract this stream to get
an unauthorized response.

Also send the track_authorization property returned in the track info,
like the website does and allow duplicate MP3 formats in progressive and
HLS delivery methods to be returned.
This commit is contained in:
AudricV 2025-01-19 00:08:26 +01:00
parent 3a33cefbc0
commit 7607688cb0
No known key found for this signature in database
GPG Key ID: DA92EC7905614198

View File

@ -6,7 +6,6 @@ import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsing
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromTrackObject;
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAvatarUrl;
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom;
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
@ -170,14 +169,12 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
}
try {
final JsonArray transcodings = track.getObject("media").getArray("transcodings");
final JsonArray transcodings = track.getObject("media")
.getArray("transcodings");
if (!isNullOrEmpty(transcodings)) {
// Get information about what stream formats are available
extractAudioStreams(transcodings, checkMp3ProgressivePresence(transcodings),
audioStreams);
extractAudioStreams(transcodings, audioStreams);
}
extractDownloadableFileIfAvailable(audioStreams);
} catch (final NullPointerException e) {
throw new ExtractionException("Could not get audio streams", e);
}
@ -185,19 +182,16 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
return audioStreams;
}
private static boolean checkMp3ProgressivePresence(@Nonnull final JsonArray transcodings) {
return transcodings.stream()
.filter(JsonObject.class::isInstance)
.map(JsonObject.class::cast)
.anyMatch(transcodingJsonObject -> transcodingJsonObject.getString("preset")
.contains("mp3") && transcodingJsonObject.getObject("format")
.getString("protocol").equals("progressive"));
}
@Nonnull
private String getTranscodingUrl(final String endpointUrl)
throws IOException, ExtractionException {
final String apiStreamUrl = endpointUrl + "?client_id=" + clientId();
String apiStreamUrl = endpointUrl + "?client_id=" + clientId();
final String trackAuthorization = track.getString("track_authorization");
if (!isNullOrEmpty(trackAuthorization)) {
apiStreamUrl += "&track_authorization=" + trackAuthorization;
}
final String response = NewPipe.getDownloader().get(apiStreamUrl).responseBody();
final JsonObject urlObject;
try {
@ -209,27 +203,7 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
return urlObject.getString("url");
}
@Nullable
private String getDownloadUrl(@Nonnull final String trackId)
throws IOException, ExtractionException {
final String response = NewPipe.getDownloader().get(SOUNDCLOUD_API_V2_URL + "tracks/"
+ trackId + "/download" + "?client_id=" + clientId()).responseBody();
final JsonObject downloadJsonObject;
try {
downloadJsonObject = JsonParser.object().from(response);
} catch (final JsonParserException e) {
throw new ParsingException("Could not parse download URL", e);
}
final String redirectUri = downloadJsonObject.getString("redirectUri");
if (!isNullOrEmpty(redirectUri)) {
return redirectUri;
}
return null;
}
private void extractAudioStreams(@Nonnull final JsonArray transcodings,
final boolean mp3ProgressiveInStreams,
final List<AudioStream> audioStreams) {
transcodings.stream()
.filter(JsonObject.class::isInstance)
@ -244,23 +218,23 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
final String preset = transcoding.getString("preset", ID_UNKNOWN);
final String protocol = transcoding.getObject("format")
.getString("protocol");
if (protocol.contains("encrypted")) {
// Skip DRM-protected streams, which have encrypted in their protocol
// name
return;
}
final AudioStream.Builder builder = new AudioStream.Builder()
.setId(preset);
final boolean isHls = protocol.equals("hls");
if (isHls) {
if (protocol.equals("hls")) {
builder.setDeliveryMethod(DeliveryMethod.HLS);
}
builder.setContent(getTranscodingUrl(url), true);
if (preset.contains("mp3")) {
// Don't add the MP3 HLS stream if there is a progressive stream
// present because both have the same bitrate
if (mp3ProgressiveInStreams && isHls) {
return;
}
builder.setMediaFormat(MediaFormat.MP3);
builder.setAverageBitrate(128);
} else if (preset.contains("opus")) {
@ -283,39 +257,6 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
});
}
/**
* Add the downloadable format if it is available.
*
* <p>
* A track can have the {@code downloadable} boolean set to {@code true}, but it doesn't mean
* we can download it.
* </p>
*
* <p>
* If the value of the {@code has_download_left} boolean is {@code true}, the track can be
* downloaded, and not otherwise.
* </p>
*
* @param audioStreams the audio streams to which the downloadable file is added
*/
public void extractDownloadableFileIfAvailable(final List<AudioStream> audioStreams) {
if (track.getBoolean("downloadable") && track.getBoolean("has_downloads_left")) {
try {
final String downloadUrl = getDownloadUrl(getId());
if (!isNullOrEmpty(downloadUrl)) {
audioStreams.add(new AudioStream.Builder()
.setId("original-format")
.setContent(downloadUrl, true)
.setAverageBitrate(UNKNOWN_BITRATE)
.build());
}
} catch (final Exception ignored) {
// If something went wrong when trying to get the download URL, ignore the
// exception throw because this "stream" is not necessary to play the track
}
}
}
@Override
public List<VideoStream> getVideoStreams() {
return Collections.emptyList();