I want to get direct audio's URL from SoundCloud's URL without using any third-party service like soundcloudtomp3.app. Is there any given API available to handle it?
Asked
Active
Viewed 574 times
-3
-
oh, no. I can't ask questions anymore. hic. – logbasex Aug 10 '21 at 19:24
1 Answers
1
After several hours of tirelessly searching Google for this question, I finally found out the answer by observing network requests before and after audio load in the browser's DevTools.
These steps are:
- Using this API to get audio's info from a specific URL:
https://api-widget.soundcloud.com/resolve?url=SOUNDLCOUD_URL&format=json&client_id=CLIENT_ID
- Get media stream URL with JSONpath:
STREAM_URL = $.media.transcodings[1].url
- Get track authorization token:
TRACK_AUTHORIZATION = $.track_authorization
- Using this info in the above steps to get direct audio URL:
https://STREAM_URL?client_id=CLIENT_ID&track_authorization=TRACK_AUTHORIZATION
My java code.
public class SoundCloudAPIServiceImpl implements SoundCloudAPIService{
private static final String SOUNDCLOUD_URL_REGEX = "^\n" +
"((?:https?:)?//)? #protocol\n" +
"(www\\.|m\\.)? #sub-domain\n" +
"(soundcloud\\.com|snd\\.sc) #domain name\n" +
"/(.*) #audio path\n" +
"$";
private static final String SOUNDCLOUD_RESOLVE_URL_API ="https://api-widget.soundcloud.com/resolve";
private static final String REGEX_SPLIT_STRING_BY_SPACE_NOT_INSIDE_QUOTE = "([^\"]\\S*|\".+?\")\\s*";
private static final String TRACK_AUTHORIZATION = "track_authorization";
@Value("${soundcloud.browser.client.id}")
private String clientId;
@Override
public GrabAudioInfo grabInfo(String soundCloudUrl) throws EkoBaseException {
Matcher soundCloudUrlMatcher = Pattern
.compile(SOUNDCLOUD_URL_REGEX, Pattern.COMMENTS | Pattern.CASE_INSENSITIVE)
.matcher(soundCloudUrl);
if (soundCloudUrlMatcher.find()) {
try {
String targetUrl = UriComponentsBuilder
.fromUriString(SOUNDCLOUD_RESOLVE_URL_API)
.queryParam(DBConst.URL, soundCloudUrl)
.queryParam(DBConst.FORMAT, DBConst.JSON)
.queryParam(DBConst.CLIENT_ID, clientId)
.build()
.toUriString();
String responseJson = IOUtils.toString(new URL(targetUrl), StandardCharsets.UTF_8);
DocumentContext documentContext = JsonPath.parse(responseJson);
LinkedHashMap<Object, Object> responseMap = JsonPath.read(responseJson, "$");
if (responseMap.size() > 0) {
String title = documentContext.read("$.title").toString();
String description = documentContext.read("$.description").toString();
List<String> tags = this.getTags(documentContext);
String directAudioUrl = this.getDirectAudioUrl(documentContext);
String artworkUrl = documentContext.read("$.artwork_url").toString();
String artworkOriginalSizeUrl = artworkUrl.replace("-large", "-original");
BufferedImage bufferedImage = ImageIO.read(new URL(artworkOriginalSizeUrl));
Integer height = bufferedImage.getHeight();
Integer width = bufferedImage.getWidth();
GrabAudioInfo grabAudioInfo = new GrabAudioInfo();
grabAudioInfo.setTitle(title);
grabAudioInfo.setDescription(description);
grabAudioInfo.setTags(tags);
grabAudioInfo.setDirectAudioUrl(directAudioUrl);
grabAudioInfo.setThumbnailUrl(artworkOriginalSizeUrl);
grabAudioInfo.setWidth(width);
grabAudioInfo.setHeight(height);
return grabAudioInfo;
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
log.info("Invalid SoundCloud URL: {}", soundCloudUrl);
throw new EkoBaseException(ErrorInfo.INVALID_AUDIO_URL_ERROR);
}
return null;
}
private List<String> getTags(DocumentContext documentContext) {
List<String> tags = new ArrayList<>();
String tagListStr = documentContext.read("$.tag_list");
Matcher m = Pattern.compile(REGEX_SPLIT_STRING_BY_SPACE_NOT_INSIDE_QUOTE).matcher(tagListStr);
while (m.find()) {
tags.add(m.group(1));
}
return tags;
}
private String getDirectAudioUrl(DocumentContext documentContext) throws IOException {
String directAudioBaseUrl = documentContext.read("$.media.transcodings[1].url").toString();
String trackAuthorization = documentContext.read("$.track_authorization").toString();
String directAudioAPI = UriComponentsBuilder
.fromUriString(directAudioBaseUrl)
.queryParam(DBConst.CLIENT_ID, clientId)
.queryParam(TRACK_AUTHORIZATION, trackAuthorization)
.queryParam(DBConst.FORMAT, DBConst.JSON)
.build()
.toUriString();
String directAudioAPIResponse = IOUtils.toString(new URL(directAudioAPI), StandardCharsets.UTF_8);
return JsonPath.parse(directAudioAPIResponse).read("url").toString();
}
//third-party solution.
private void convertToDirectAudioUrl(String soundCloudUrl) {
WebDriverManager.getInstance(DriverManagerType.CHROME).setup();
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless", "--disable-gpu", "--window-size=1280,800", "--ignore-certificate-errors");
WebDriver webDriver = new ChromeDriver(options);
webDriver.get("https://soundcloudtomp3.app/");
WebDriverWait wait = new WebDriverWait(webDriver, 15);
//https://stackoverflow.com/questions/62176652/no-instances-of-type-variables-v-exist-so-that-expectedconditionboolean-co
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("/html/body/div[2]/div/center/form/div/input"))).sendKeys(soundCloudUrl);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("/html/body/div[2]/div/center/form/div/span/button"))).click();
WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.xpath("/html/body/div[2]/main/div/div/div[3]/div[2]/table/tbody/tr[2]/td/a"))
);
String directAudioUrl = element.getAttribute("href");
webDriver.quit();
}
}

logbasex
- 1,688
- 1
- 16
- 22
-
What is this code part of? STREAM_URL = $.media.transcodings[1].url Do you have whole snippet? – Toniq Aug 12 '21 at 12:01
-