Merge pull request #1269 from AudricV/snd_no_drm_streams

[Soundcloud] Remove DRM-protected and downloadable formats extraction
This commit is contained in:
Stypox 2025-02-03 20:35:20 +01:00 committed by GitHub
commit 186e32c9db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 38 additions and 89 deletions

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.getAllImagesFromTrackObject;
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAvatarUrl; 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.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.stream.Stream.ID_UNKNOWN;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
@ -170,14 +169,12 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
} }
try { try {
final JsonArray transcodings = track.getObject("media").getArray("transcodings"); final JsonArray transcodings = track.getObject("media")
.getArray("transcodings");
if (!isNullOrEmpty(transcodings)) { if (!isNullOrEmpty(transcodings)) {
// Get information about what stream formats are available // Get information about what stream formats are available
extractAudioStreams(transcodings, checkMp3ProgressivePresence(transcodings), extractAudioStreams(transcodings, audioStreams);
audioStreams);
} }
extractDownloadableFileIfAvailable(audioStreams);
} catch (final NullPointerException e) { } catch (final NullPointerException e) {
throw new ExtractionException("Could not get audio streams", e); throw new ExtractionException("Could not get audio streams", e);
} }
@ -185,19 +182,16 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
return audioStreams; 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 @Nonnull
private String getTranscodingUrl(final String endpointUrl) private String getTranscodingUrl(final String endpointUrl)
throws IOException, ExtractionException { 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 String response = NewPipe.getDownloader().get(apiStreamUrl).responseBody();
final JsonObject urlObject; final JsonObject urlObject;
try { try {
@ -209,27 +203,7 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
return urlObject.getString("url"); 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, private void extractAudioStreams(@Nonnull final JsonArray transcodings,
final boolean mp3ProgressiveInStreams,
final List<AudioStream> audioStreams) { final List<AudioStream> audioStreams) {
transcodings.stream() transcodings.stream()
.filter(JsonObject.class::isInstance) .filter(JsonObject.class::isInstance)
@ -244,23 +218,23 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
final String preset = transcoding.getString("preset", ID_UNKNOWN); final String preset = transcoding.getString("preset", ID_UNKNOWN);
final String protocol = transcoding.getObject("format") final String protocol = transcoding.getObject("format")
.getString("protocol"); .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() final AudioStream.Builder builder = new AudioStream.Builder()
.setId(preset); .setId(preset);
final boolean isHls = protocol.equals("hls"); if (protocol.equals("hls")) {
if (isHls) {
builder.setDeliveryMethod(DeliveryMethod.HLS); builder.setDeliveryMethod(DeliveryMethod.HLS);
} }
builder.setContent(getTranscodingUrl(url), true); builder.setContent(getTranscodingUrl(url), true);
if (preset.contains("mp3")) { 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.setMediaFormat(MediaFormat.MP3);
builder.setAverageBitrate(128); builder.setAverageBitrate(128);
} else if (preset.contains("opus")) { } 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 @Override
public List<VideoStream> getVideoStreams() { public List<VideoStream> getVideoStreams() {
return Collections.emptyList(); return Collections.emptyList();

View File

@ -24,6 +24,7 @@ import javax.annotation.Nullable;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.fail;
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
public class SoundcloudStreamExtractorTest { public class SoundcloudStreamExtractorTest {
@ -188,26 +189,33 @@ public class SoundcloudStreamExtractorTest {
public void testAudioStreams() throws Exception { public void testAudioStreams() throws Exception {
super.testAudioStreams(); super.testAudioStreams();
final List<AudioStream> audioStreams = extractor.getAudioStreams(); final List<AudioStream> audioStreams = extractor.getAudioStreams();
assertEquals(2, audioStreams.size()); assertEquals(3, audioStreams.size()); // 2 MP3 streams (1 progressive, 1 HLS) and 1 OPUS
audioStreams.forEach(audioStream -> { audioStreams.forEach(audioStream -> {
final DeliveryMethod deliveryMethod = audioStream.getDeliveryMethod(); final DeliveryMethod deliveryMethod = audioStream.getDeliveryMethod();
final String mediaUrl = audioStream.getContent(); final String mediaUrl = audioStream.getContent();
if (audioStream.getFormat() == MediaFormat.OPUS) { if (audioStream.getFormat() == MediaFormat.OPUS) {
// Assert that it's an OPUS 64 kbps media URL with a single range which comes
// from an HLS SoundCloud CDN
ExtractorAsserts.assertContains("-hls-opus-media.sndcdn.com", mediaUrl);
ExtractorAsserts.assertContains(".64.opus", mediaUrl);
assertSame(DeliveryMethod.HLS, deliveryMethod, assertSame(DeliveryMethod.HLS, deliveryMethod,
"Wrong delivery method for stream " + audioStream.getId() + ": " "Wrong delivery method for stream " + audioStream.getId() + ": "
+ deliveryMethod); + deliveryMethod);
} else if (audioStream.getFormat() == MediaFormat.MP3) { // Assert it's an OPUS 64 kbps media playlist URL which comes from an HLS
// Assert that it's a MP3 128 kbps media URL which comes from a progressive
// SoundCloud CDN // SoundCloud CDN
ExtractorAsserts.assertContains("-media.sndcdn.com/bKOA7Pwbut93.128.mp3", ExtractorAsserts.assertContains("-hls-opus-media.sndcdn.com", mediaUrl);
mediaUrl); ExtractorAsserts.assertContains(".64.opus", mediaUrl);
assertSame(DeliveryMethod.PROGRESSIVE_HTTP, deliveryMethod, } else if (audioStream.getFormat() == MediaFormat.MP3) {
"Wrong delivery method for stream " + audioStream.getId() + ": " if (deliveryMethod == DeliveryMethod.PROGRESSIVE_HTTP) {
+ deliveryMethod); // Assert it's a MP3 128 kbps media URL which comes from a progressive
// SoundCloud CDN
ExtractorAsserts.assertContains("-media.sndcdn.com/bKOA7Pwbut93.128.mp3",
mediaUrl);
} else if (deliveryMethod == DeliveryMethod.HLS) {
// Assert it's a MP3 128 kbps media HLS playlist URL which comes from an HLS
// SoundCloud CDN
ExtractorAsserts.assertContains("-hls-media.sndcdn.com", mediaUrl);
ExtractorAsserts.assertContains(".128.mp3", mediaUrl);
} else {
fail("Wrong delivery method for stream " + audioStream.getId() + ": "
+ deliveryMethod);
}
} }
}); });
} }