Soundcloud 获取直接音频 URL?
Soundcloud get direct audio URL?
我想从 SoundCloud 的 URL 获取直接音频 URL,而不使用任何第三方服务,如 soundcloudtomp3.app。有没有给定的 API 可以处理它?
这个问题不知疲倦地搜索Google几个小时后,通过在浏览器的DevTools中观察音频加载前后的网络请求,终于找到了答案。
这些步骤是:
- 使用此 API 从特定 URL 获取音频信息:
https://api-widget.soundcloud.com/resolve?url=SOUNDLCOUD_URL&format=json&client_id=CLIENT_ID
- 使用 JSONpath 获取媒体流 URL:
STREAM_URL = $.media.transcodings[1].url
- 获取曲目授权令牌:
TRACK_AUTHORIZATION = $.track_authorization
- 在上述步骤中使用此信息获取直接音频URL:
https://STREAM_URL?client_id=CLIENT_ID&track_authorization=TRACK_AUTHORIZATION
我的 java 代码。
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);
//
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();
}
}
我想从 SoundCloud 的 URL 获取直接音频 URL,而不使用任何第三方服务,如 soundcloudtomp3.app。有没有给定的 API 可以处理它?
这个问题不知疲倦地搜索Google几个小时后,通过在浏览器的DevTools中观察音频加载前后的网络请求,终于找到了答案。
这些步骤是:
- 使用此 API 从特定 URL 获取音频信息:
https://api-widget.soundcloud.com/resolve?url=SOUNDLCOUD_URL&format=json&client_id=CLIENT_ID
- 使用 JSONpath 获取媒体流 URL:
STREAM_URL = $.media.transcodings[1].url
- 获取曲目授权令牌:
TRACK_AUTHORIZATION = $.track_authorization
- 在上述步骤中使用此信息获取直接音频URL:
https://STREAM_URL?client_id=CLIENT_ID&track_authorization=TRACK_AUTHORIZATION
我的 java 代码。
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);
//
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();
}
}