api - Soundcloud 获取直接音频 URL?
问题描述
我想从 SoundCloud 的 URL 获取直接音频的 URL,而不使用soundcloudtomp3.app 之类的任何第三方服务。是否有任何给定的 API 可用于处理它?
解决方案
在谷歌不知疲倦地搜索这个问题几个小时后,我终于通过在浏览器的 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);
//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();
}
}
推荐阅读
- javascript - 无法使用 jquery 从 API 获取天气值
- javascript - 如何更正搜索输入中的逻辑?角
- html - 我的 Html 的页脚没有出现,我做错了什么?
- javascript - 使用命名空间模块中的 mapState 和 mapMutations
- api - Agora 无法启动云录制
- python - 数组多维python 3的复制列表
- teamcity - build.vcs.number 的值应该是多少?
- syntax - 错误!加载 YAML 时出现语法错误。AWX/Ansible
- javascript - Javascript 菜单切换在上线后无法在移动设备上运行
- php - 如何在 codeigniter 中包含和使用自己的类(来自插件)