From da827778f8c5d2f0cfc56b297099ba58454c38ed Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 26 Mar 2021 16:45:41 +0100 Subject: Add soundcloud --- src/plugins/Pornhub.cpp | 8 +- src/plugins/Soundcloud.cpp | 287 +++++++++++++++++++++++++++++++++++++++++++++ src/plugins/Spotify.cpp | 4 +- src/plugins/Youtube.cpp | 10 +- 4 files changed, 298 insertions(+), 11 deletions(-) create mode 100644 src/plugins/Soundcloud.cpp (limited to 'src/plugins') diff --git a/src/plugins/Pornhub.cpp b/src/plugins/Pornhub.cpp index b4e908a..74b66b6 100644 --- a/src/plugins/Pornhub.cpp +++ b/src/plugins/Pornhub.cpp @@ -140,13 +140,13 @@ namespace QuickMedia { return search_result_to_plugin_result(get_videos_in_page(url, is_tor_enabled(), result_items)); } - PluginResult PornhubSearchPage::submit(const std::string&, const std::string&, std::vector &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); + PluginResult PornhubSearchPage::submit(const std::string&, const std::string &url, std::vector &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); return PluginResult::OK; } - PluginResult PornhubRelatedVideosPage::submit(const std::string&, const std::string&, std::vector &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); + PluginResult PornhubRelatedVideosPage::submit(const std::string&, const std::string &url, std::vector &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); return PluginResult::OK; } 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 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(); + 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 &result_tabs) { + if(url.empty()) + return PluginResult::ERR; + + if(url == "track") { + auto body = create_body(); + body->items = static_cast(submit_body_item->extra.get())->tracks; + result_tabs.push_back(Tab{std::move(body), std::make_unique(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(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(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 diff --git a/src/plugins/Spotify.cpp b/src/plugins/Spotify.cpp index 14f9831..f56ed6c 100644 --- a/src/plugins/Spotify.cpp +++ b/src/plugins/Spotify.cpp @@ -274,8 +274,8 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult SpotifyEpisodeListPage::submit(const std::string &, const std::string &, std::vector &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); + PluginResult SpotifyEpisodeListPage::submit(const std::string &, const std::string &url, std::vector &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); return PluginResult::OK; } } \ No newline at end of file diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 99227d5..3813068 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -572,7 +572,7 @@ namespace QuickMedia { // TODO: Make all pages (for all services) lazy fetch in a similar manner! result_tabs.push_back(Tab{create_body(), std::make_unique(program, url, "", title), create_search_bar("Search...", 350)}); } else { - result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); + result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); } return PluginResult::OK; } @@ -1099,10 +1099,10 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult YoutubeChannelPage::submit(const std::string&, const std::string&, std::vector &result_tabs) { + PluginResult YoutubeChannelPage::submit(const std::string&, const std::string &url, std::vector &result_tabs) { if(url.empty()) return PluginResult::OK; - result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); + result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); return PluginResult::OK; } @@ -1126,8 +1126,8 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult YoutubeRelatedVideosPage::submit(const std::string&, const std::string&, std::vector &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); + PluginResult YoutubeRelatedVideosPage::submit(const std::string&, const std::string &url, std::vector &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); return PluginResult::OK; } -- cgit v1.2.3