aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/Soundcloud.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-03-26 16:45:41 +0100
committerdec05eba <dec05eba@protonmail.com>2021-03-26 16:45:41 +0100
commitda827778f8c5d2f0cfc56b297099ba58454c38ed (patch)
tree9e17efe65eca94a23374aa8ea00da0da50d45bfe /src/plugins/Soundcloud.cpp
parentd96e65b2abf2a569a4be4c160fa30a504abdb2fc (diff)
Add soundcloud
Diffstat (limited to 'src/plugins/Soundcloud.cpp')
-rw-r--r--src/plugins/Soundcloud.cpp287
1 files changed, 287 insertions, 0 deletions
diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp
new file mode 100644
index 0000000..cfb6011
--- /dev/null
+++ b/src/plugins/Soundcloud.cpp
@@ -0,0 +1,287 @@
+#include "../../plugins/Soundcloud.hpp"
+#include "../../include/NetUtils.hpp"
+#include "../../include/StringUtils.hpp"
+#include "../../include/Scale.hpp"
+
+namespace QuickMedia {
+ static std::string client_id = "Na04L87fnpWDMVCCW2ngWldN4JMoLTAc";
+
+ class SoundcloudPlaylist : public BodyItemExtra {
+ public:
+ BodyItems tracks;
+ };
+
+ // Return empty string if transcoding files are not found
+ static std::string get_best_transcoding_audio_url(const Json::Value &media_json) {
+ const Json::Value &transcodings_json = media_json["transcodings"];
+ if(transcodings_json.isArray() && !transcodings_json.empty() && transcodings_json[0].isObject()) {
+ const Json::Value &transcoding_url = transcodings_json[0]["url"];
+ if(transcoding_url.isString())
+ return transcoding_url.asString();
+ }
+ return "";
+ }
+
+ static std::string duration_to_descriptive_string(int64_t seconds) {
+ seconds /= 1000;
+ time_t minutes = seconds / 60;
+ time_t hours = minutes / 60;
+
+ std::string str;
+ if(hours >= 1) {
+ str = std::to_string(hours) + " hour" + (hours == 1 ? "" : "s");
+ seconds -= (hours * 60 * 60);
+ }
+
+ if(minutes >= 1) {
+ if(!str.empty())
+ str += ", ";
+ str += std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s");
+ seconds -= (minutes * 60);
+ }
+
+ if(!str.empty() || seconds > 0) {
+ if(!str.empty())
+ str += ", ";
+ str += std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s");
+ }
+
+ return str;
+ }
+
+ static std::string collection_item_get_duration(const Json::Value &item_json) {
+ const Json::Value &full_duration_json = item_json["full_duration"];
+ if(full_duration_json.isInt64())
+ return duration_to_descriptive_string(full_duration_json.asInt64());
+
+ const Json::Value &duration_json = item_json["duration"];
+ if(duration_json.isInt64())
+ return duration_to_descriptive_string(duration_json.asInt64());
+
+ return "";
+ }
+
+ static std::shared_ptr<BodyItem> parse_collection_item(const Json::Value &item_json) {
+ std::string title;
+
+ const Json::Value &title_json = item_json["title"];
+ const Json::Value &username_json = item_json["username"];
+ if(title_json.isString())
+ title = title_json.asString();
+ else if(username_json.isString())
+ title = username_json.asString();
+ else
+ return nullptr;
+
+ auto body_item = BodyItem::create(std::move(title));
+ std::string description;
+
+ const Json::Value &media_json = item_json["media"];
+ if(media_json.isObject())
+ body_item->url = get_best_transcoding_audio_url(media_json);
+
+ if(body_item->url.empty()) {
+ const Json::Value &tracks_json = item_json["tracks"];
+ if(tracks_json.isArray()) {
+ auto playlist = std::make_shared<SoundcloudPlaylist>();
+ for(const Json::Value &track_json : tracks_json) {
+ if(!track_json.isObject())
+ continue;
+
+ auto track = parse_collection_item(track_json);
+ if(track)
+ playlist->tracks.push_back(std::move(track));
+ }
+
+ description = "Playlist with " + std::to_string(playlist->tracks.size()) + " track" + (playlist->tracks.size() == 1 ? "" : "s");
+ body_item->extra = std::move(playlist);
+ body_item->url = "track";
+ }
+ }
+
+ if(body_item->url.empty()) {
+ const Json::Value &id_json = item_json["id"];
+ if(id_json.isInt64())
+ body_item->url = "https://api-v2.soundcloud.com/stream/users/" + std::to_string(id_json.asInt64());
+ }
+
+ const Json::Value &artwork_url_json = item_json["artwork_url"];
+ const Json::Value &avatar_url_json = item_json["avatar_url"];
+ if(artwork_url_json.isString()) {
+ // For larger thumbnails
+ /*
+ if(strstr(artwork_url_json.asCString(), "-large") != 0) {
+ std::string artwork_url = artwork_url_json.asString();
+ string_replace_all(artwork_url, "-large", "-t200x200");
+ body_item->thumbnail_url = std::move(artwork_url);
+ body_item->thumbnail_size.x = 200;
+ body_item->thumbnail_size.y = 200;
+ } else {
+ body_item->thumbnail_url = artwork_url_json.asString();
+ body_item->thumbnail_size.x = 100;
+ body_item->thumbnail_size.y = 100;
+ }
+ */
+ body_item->thumbnail_url = artwork_url_json.asString();
+ body_item->thumbnail_size.x = 100;
+ body_item->thumbnail_size.y = 100;
+ } else if(avatar_url_json.isString()) {
+ body_item->thumbnail_url = avatar_url_json.asString();
+ body_item->thumbnail_size.x = 100;
+ body_item->thumbnail_size.y = 100;
+ body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ }
+
+ std::string duration_str = collection_item_get_duration(item_json);
+ if(!duration_str.empty()) {
+ if(!description.empty())
+ description += '\n';
+ description += std::move(duration_str);
+ }
+
+ body_item->set_description(std::move(description));
+ return body_item;
+ }
+
+ static PluginResult parse_user_page(const Json::Value &json_root, BodyItems &result_items, std::string &next_href) {
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &next_href_json = json_root["next_href"];
+ if(next_href_json.isString())
+ next_href = next_href_json.asString();
+
+ const Json::Value &collection_json = json_root["collection"];
+ if(!collection_json.isArray())
+ return PluginResult::ERR;
+
+ for(const Json::Value &item_json : collection_json) {
+ if(!item_json.isObject())
+ continue;
+
+ const Json::Value &track_json = item_json["track"];
+ const Json::Value &playlist_json = item_json["playlist"];
+ if(track_json.isObject()) {
+ auto body_item = parse_collection_item(track_json);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ } else if(playlist_json.isObject()) {
+ auto body_item = parse_collection_item(playlist_json);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ }
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult SoundcloudPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ if(url.empty())
+ return PluginResult::ERR;
+
+ if(url == "track") {
+ auto body = create_body();
+ body->items = static_cast<SoundcloudPlaylist*>(submit_body_item->extra.get())->tracks;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<SoundcloudPlaylistPage>(program, title), nullptr});
+ } else if(url.find("/stream/users/") != std::string::npos) {
+ std::string query_url = url + "?client_id=" + client_id + "&limit=20&offset=0&linked_partitioning=1&app_version=1616689516&app_locale=en";
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, query_url, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ auto body = create_body();
+ std::string next_href;
+ PluginResult pr = parse_user_page(json_root, body->items, next_href);
+ if(pr != PluginResult::OK) return pr;
+
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<SoundcloudUserPage>(program, title, url, std::move(next_href)), nullptr});
+ } else {
+ std::string query_url = url + "?client_id=" + client_id;
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, query_url, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &url_json = json_root["url"];
+ if(!url_json.isString())
+ return PluginResult::ERR;
+
+ result_tabs.push_back(Tab{create_body(), std::make_unique<SoundcloudAudioPage>(program, url_json.asString()), nullptr});
+ }
+
+ return PluginResult::OK;
+ }
+
+ SearchResult SoundcloudSearchPage::search(const std::string &str, BodyItems &result_items) {
+ query_urn.clear();
+ PluginResult result = get_page(str, 0, result_items);
+ if(result != PluginResult::OK)
+ return SearchResult::ERR;
+ return SearchResult::OK;
+ }
+
+ PluginResult SoundcloudSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) {
+ std::string url = "https://api-v2.soundcloud.com/search?q=";
+ url += url_param_encode(str);
+ url += "&variant_ids=2227&facet=model&client_id=" + client_id + "&limit=20&offset=" + std::to_string(page * 20) + "&linked_partitioning=1&app_version=1616689516&app_locale=en";
+ if(!query_urn.empty())
+ url += "&query_url=" + url_param_encode(query_urn);
+ else if(page > 0)
+ return PluginResult::OK;
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, url, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &query_urn_json = json_root["query_urn"];
+ if(query_urn_json.isString())
+ query_urn = query_urn_json.asString();
+
+ const Json::Value &collection_json = json_root["collection"];
+ if(!collection_json.isArray())
+ return PluginResult::ERR;
+
+ for(const Json::Value &item_json : collection_json) {
+ if(!item_json.isObject())
+ continue;
+
+ const Json::Value &kind_json = item_json["kind"];
+ if(!kind_json.isString())
+ continue;
+
+ if(strcmp(kind_json.asCString(), "user") == 0 || strcmp(kind_json.asCString(), "track") == 0 || strcmp(kind_json.asCString(), "playlist") == 0) {
+ auto body_item = parse_collection_item(item_json);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ }
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult SoundcloudUserPage::get_page(const std::string&, int page, BodyItems &result_items) {
+ while(current_page < page) {
+ PluginResult plugin_result = get_continuation_page(result_items);
+ if(plugin_result != PluginResult::OK) return plugin_result;
+ ++current_page;
+ }
+ return PluginResult::OK;
+ }
+
+ PluginResult SoundcloudUserPage::get_continuation_page(BodyItems &result_items) {
+ if(next_href.empty())
+ return PluginResult::OK;
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, next_href + "&client_id=" + client_id, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+ return parse_user_page(json_root, result_items, next_href);
+ }
+} \ No newline at end of file