aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp7
-rw-r--r--src/StringUtils.cpp22
-rw-r--r--src/VideoPlayer.cpp2
-rw-r--r--src/plugins/Fourchan.cpp2
-rw-r--r--src/plugins/ImageBoard.cpp5
-rw-r--r--src/plugins/Lbry.cpp397
-rw-r--r--src/plugins/Manganelo.cpp2
-rw-r--r--src/plugins/Peertube.cpp22
-rw-r--r--src/plugins/Soundcloud.cpp32
9 files changed, 435 insertions, 56 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 435ec3b..65d6159 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -10,6 +10,7 @@
#include "../plugins/NyaaSi.hpp"
#include "../plugins/Matrix.hpp"
#include "../plugins/Soundcloud.hpp"
+#include "../plugins/Lbry.hpp"
#include "../plugins/FileManager.hpp"
#include "../plugins/Pipe.hpp"
#include "../plugins/Saucenao.hpp"
@@ -78,6 +79,7 @@ static const std::pair<const char*, const char*> valid_plugins[] = {
std::make_pair("youtube", "yt_logo_rgb_dark_small.png"),
std::make_pair("peertube", "peertube_logo.png"),
std::make_pair("soundcloud", "soundcloud_logo.png"),
+ std::make_pair("lbry", "lbry_logo.png"),
std::make_pair("pornhub", "pornhub_logo.png"),
std::make_pair("spankbang", "spankbang_logo.png"),
std::make_pair("xvideos", "xvideos_logo.png"),
@@ -286,7 +288,7 @@ namespace QuickMedia {
static void usage() {
fprintf(stderr, "usage: quickmedia [plugin] [--no-video] [--dir <directory>] [-e <window>] [youtube-url]\n");
fprintf(stderr, "OPTIONS:\n");
- fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, youtube, peertube, soundcloud, nyaa.si, matrix, saucenao, hotexamples, anilist, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n");
+ fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, youtube, peertube, lbry, soundcloud, nyaa.si, matrix, saucenao, hotexamples, anilist, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n");
fprintf(stderr, " --no-video Only play audio when playing a video. Disabled by default\n");
fprintf(stderr, " --upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default\n");
fprintf(stderr, " --upscale-images-always Upscale manga pages using waifu2x-ncnn-vulkan, no matter what the original image resolution is. Disabled by default\n");
@@ -1046,6 +1048,7 @@ namespace QuickMedia {
create_launcher_body_item("4chan", "4chan", resources_root + "icons/4chan_launcher.png"),
create_launcher_body_item("AniList", "anilist", resources_root + "images/anilist_logo.png"),
create_launcher_body_item("Hot Examples", "hotexamples", ""),
+ create_launcher_body_item("Lbry", "lbry", "icons/lbry_launcher.png"),
create_launcher_body_item("Manga (all)", "manga", ""),
create_launcher_body_item("Mangadex", "mangadex", resources_root + "icons/mangadex_launcher.png"),
create_launcher_body_item("Mangakatana", "mangakatana", resources_root + "icons/mangakatana_launcher.png"),
@@ -1262,6 +1265,8 @@ namespace QuickMedia {
} else if(strcmp(plugin_name, "soundcloud") == 0) {
tabs.push_back(Tab{create_body(false, true), std::make_unique<SoundcloudSearchPage>(this), create_search_bar("Search...", 500)});
no_video = true;
+ } else if(strcmp(plugin_name, "lbry") == 0) {
+ tabs.push_back(Tab{create_body(false, true), std::make_unique<LbrySearchPage>(this), create_search_bar("Search...", 500)});
} else if(strcmp(plugin_name, "matrix") == 0) {
assert(!matrix);
if(create_directory_recursive(get_cache_dir().join("matrix").join("events")) != 0) {
diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp
index 5706499..494e32f 100644
--- a/src/StringUtils.cpp
+++ b/src/StringUtils.cpp
@@ -219,4 +219,26 @@ namespace QuickMedia {
else
return std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s") + " ago";
}
+
+ std::string seconds_to_duration(int seconds) {
+ seconds = std::max(0, seconds);
+
+ int minutes = seconds / 60;
+ int hours = minutes / 60;
+ char buffer[32];
+
+ if(hours >= 1) {
+ minutes -= (hours * 60);
+ seconds -= (hours * 60 * 60);
+ seconds -= (minutes * 60);
+ snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d", hours, minutes, seconds);
+ } else if(minutes >= 1) {
+ seconds -= (minutes * 60);
+ snprintf(buffer, sizeof(buffer), "%02d:%02d", minutes, seconds);
+ } else {
+ snprintf(buffer, sizeof(buffer), "00:%02d", seconds);
+ }
+
+ return buffer;
+ }
} \ No newline at end of file
diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp
index 047e525..5d2bf3c 100644
--- a/src/VideoPlayer.cpp
+++ b/src/VideoPlayer.cpp
@@ -229,7 +229,7 @@ namespace QuickMedia {
strcpy(ipc_addr.sun_path, ipc_server_path);
int flags = fcntl(ipc_socket, F_GETFL, 0);
- if(flags != -1) // TODO: Proper error handling
+ if(flags != -1)
fcntl(ipc_socket, F_SETFL, flags | O_NONBLOCK);
if(exec_program_async(args.data(), &video_process_id) != 0) {
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index f7c9910..01c8546 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -526,7 +526,7 @@ namespace QuickMedia {
additional_args.push_back({ "--form-string", "filename=" + file_get_filename(filepath) });
}
- if(pass_id.empty()) {
+ if(pass_id.empty() && !captcha_id.empty()) {
additional_args.push_back(CommandArg{"--form-string", "t-challenge=" + captcha_id});
additional_args.push_back(CommandArg{"--form-string", "t-response=" + captcha_solution});
}
diff --git a/src/plugins/ImageBoard.cpp b/src/plugins/ImageBoard.cpp
index a2ffca7..f813068 100644
--- a/src/plugins/ImageBoard.cpp
+++ b/src/plugins/ImageBoard.cpp
@@ -1,6 +1,11 @@
#include "../../plugins/ImageBoard.hpp"
+#include <SFML/Window/Clipboard.hpp>
namespace QuickMedia {
+ void ImageBoardThreadPage::copy_to_clipboard(const BodyItem *body_item) const {
+ sf::Clipboard::setString(sf::String::fromUtf8(body_item->get_description().begin(), body_item->get_description().end()));
+ }
+
std::unique_ptr<RelatedVideosPage> ImageBoardThreadPage::create_related_videos_page(Program*) {
return nullptr;
}
diff --git a/src/plugins/Lbry.cpp b/src/plugins/Lbry.cpp
new file mode 100644
index 0000000..c35e430
--- /dev/null
+++ b/src/plugins/Lbry.cpp
@@ -0,0 +1,397 @@
+#include "../../plugins/Lbry.hpp"
+#include "../../include/Notification.hpp"
+#include "../../include/StringUtils.hpp"
+#include "../../include/Theme.hpp"
+#include <json/value.h>
+#include <json/writer.h>
+
+// TODO: Images, music, regular files
+
+namespace QuickMedia {
+ static void *search_type_video = (void*)0;
+ static void *search_type_channel = (void*)1;
+
+ static bool handle_error(const Json::Value &json_root, std::string &err_str) {
+ const Json::Value &error_json = json_root["error"];
+ if(!error_json.isObject())
+ return false;
+
+ const Json::Value &code_json = error_json["code"];
+ const Json::Value &message_json = error_json["message"];
+ if(message_json.isString())
+ err_str += "message: " + message_json.asString();
+ if(code_json.isString())
+ err_str += " (code: " + code_json.asString() + ")";
+ return true;
+ }
+
+ static std::shared_ptr<BodyItem> resolve_claim_parse_result(const Json::Value &result_json, time_t time_now) {
+ if(!result_json.isObject())
+ return nullptr;
+
+ const Json::Value &canonical_url_json = result_json["canonical_url"];
+ const Json::Value &claim_id_json = result_json["claim_id"];
+ const Json::Value &value_type_json = result_json["value_type"];
+ if(!canonical_url_json.isString() || !claim_id_json.isString() || !value_type_json.isString())
+ return nullptr;
+
+ const Json::Value &value_json = result_json["value"];
+ if(!value_json.isObject())
+ return nullptr;
+
+ const Json::Value &title_json = value_json["title"];
+ if(!title_json.isString())
+ return nullptr;
+
+ auto body_item = BodyItem::create(title_json.asString());
+
+ bool is_channel = false;
+ // TODO: Support other types than stream and channel
+ if(strcmp(value_type_json.asCString(), "channel") == 0) {
+ body_item->url = claim_id_json.asString();
+ body_item->userdata = search_type_channel;
+ is_channel = true;
+ } else if(strcmp(value_type_json.asCString(), "stream") == 0) {
+ body_item->url = canonical_url_json.asString();
+ body_item->userdata = search_type_video;
+
+ // Skip livestreams for now as they are pretty broken on lbry.
+ // Livestream requests work by doing GET https://api.live.odysee.com/v1/odysee/live/<claim_id>
+ // then get stream url with .data.url. If that is missing then there is no livestream going on. What to do then?
+ // TODO: Add livestreams when lbry fixes them.
+ const Json::Value &stream_type_json = value_json["stream_type"];
+ if(!stream_type_json.isString() || strcmp(stream_type_json.asCString(), "video") != 0)
+ return nullptr;
+ }
+ body_item->thumbnail_size = { 177, 100 };
+
+ const Json::Value &thumbnail_json = value_json["thumbnail"];
+ if(thumbnail_json.isObject()) {
+ const Json::Value &url_json = thumbnail_json["url"];
+ if(url_json.isString()) {
+ if(strstr(url_json.asCString(), "ytimg.com"))
+ body_item->thumbnail_url = url_json.asString();
+ else
+ body_item->thumbnail_url = url_json.asString() + "?quality=85&width=177&height=100";
+ }
+ }
+
+ std::string description;
+
+ if(is_channel) {
+ const Json::Value &meta_json = result_json["meta"];
+ if(meta_json.isObject()) {
+ const Json::Value &claims_in_channel_json = meta_json["claims_in_channel"];
+ if(claims_in_channel_json.isInt()) {
+ const int claims_in_channel = claims_in_channel_json.asInt();
+ description = std::to_string(claims_in_channel) + " upload" + (claims_in_channel == 0 ? "" : "s");
+ }
+ }
+
+ const Json::Value &name_json = result_json["name"];
+ if(name_json.isString()) {
+ if(!description.empty())
+ description += '\n';
+ description += name_json.asString();
+ }
+ } else {
+ const Json::Value &timestamp_json = result_json["timestamp"];
+ if(timestamp_json.isInt64())
+ description = seconds_to_relative_time_str(time_now - timestamp_json.asInt64());
+ }
+
+ const Json::Value &video_json = value_json["video"];
+ if(video_json.isObject()) {
+ const Json::Value duration_json = video_json["duration"];
+ if(duration_json.isInt()) {
+ if(!description.empty())
+ description += '\n';
+ description += seconds_to_duration(duration_json.asInt());
+ }
+ }
+
+ const Json::Value &signing_channel_json = result_json["signing_channel"];
+ if(signing_channel_json.isObject()) {
+ const Json::Value &name_json = signing_channel_json["name"];
+ if(name_json.isString()) {
+ if(!description.empty())
+ description += '\n';
+ description += name_json.asString();
+ }
+ }
+
+ if(!description.empty()) {
+ body_item->set_description(std::move(description));
+ body_item->set_description_color(get_theme().faded_text_color);
+ }
+
+ return body_item;
+ }
+
+ static PluginResult resolve_claims(Page *page, const Json::Value &request_json, BodyItems &result_items) {
+ if(!request_json.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &method_json = request_json["method"];
+ if(!method_json.isString())
+ return PluginResult::ERR;
+
+ std::string url = "https://api.na-backend.odysee.com/api/v1/proxy?m=" + method_json.asString();
+
+ Json::StreamWriterBuilder json_builder;
+ json_builder["commentStyle"] = "None";
+ json_builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", Json::writeString(json_builder, request_json) }
+ };
+
+ Json::Value json_root;
+ DownloadResult download_result = page->download_json(json_root, url, std::move(additional_args), true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ std::string err_str;
+ if(handle_error(json_root, err_str)) {
+ show_notification("QuickMedia", "Lbry search failed, error: " + err_str, Urgency::CRITICAL);
+ return PluginResult::ERR;
+ }
+
+ const Json::Value &result_json = json_root["result"];
+ if(!result_json.isObject())
+ return PluginResult::ERR;
+
+ const time_t time_now = time(nullptr);
+ const Json::Value &items_json = result_json["items"];
+ if(items_json.isArray()) {
+ // Channel search
+ for(const Json::Value &result_json : items_json) {
+ auto body_item = resolve_claim_parse_result(result_json, time_now);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ }
+ } else {
+ // Global search
+ for(Json::Value::const_iterator it = result_json.begin(); it != result_json.end(); ++it) {
+ auto body_item = resolve_claim_parse_result(*it, time_now);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ }
+ }
+
+ return PluginResult::OK;
+ }
+
+ SearchResult LbrySearchPage::search(const std::string &str, BodyItems &result_items) {
+ return plugin_result_to_search_result(get_page(str, 0, result_items));
+ }
+
+ PluginResult LbrySearchPage::get_page(const std::string &str, int page, BodyItems &result_items) {
+ if(str.empty())
+ return PluginResult::OK;
+
+ // TODO: Support other types than stream and channel
+ std::string url = "https://lighthouse.odysee.com/search?s=" + url_param_encode(str) + "&size=20&from=" + std::to_string(page * 20) + "&nsfw=false&claimType=stream,channel";
+ if(!channel_id.empty())
+ url += "&channel_id=" + channel_id;
+
+ Json::Value json_root;
+ DownloadResult download_result = download_json(json_root, url, {}, true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ if(!json_root.isArray())
+ return PluginResult::ERR;
+
+ Json::Value request_json(Json::objectValue);
+ request_json["id"] = (int64_t)time(nullptr) * 1000;
+ request_json["jsonrpc"] = "2.0";
+ request_json["method"] = "resolve";
+
+ Json::Value request_params_json(Json::objectValue);
+ request_params_json["include_purchase_receipt"] = true;
+
+ Json::Value urls_json(Json::arrayValue);
+
+ for(const Json::Value &claim_json : json_root) {
+ if(!claim_json.isObject())
+ continue;
+
+ const Json::Value &claim_id_json = claim_json["claimId"];
+ const Json::Value &name_json = claim_json["name"];
+ if(!claim_id_json.isString() || !name_json.isString())
+ continue;
+
+ urls_json.append("lbry://" + name_json.asString() + "#" + claim_id_json.asString());
+ }
+
+ request_params_json["urls"] = std::move(urls_json);
+ request_json["params"] = std::move(request_params_json);
+ return resolve_claims(this, request_json, result_items);
+ }
+
+ PluginResult LbrySearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ if(submit_body_item->userdata == search_type_video)
+ result_tabs.push_back(Tab{ nullptr, std::make_unique<LbryVideoPage>(program, title, url), nullptr });
+ else if(submit_body_item->userdata == search_type_channel)
+ result_tabs.push_back(Tab{ create_body(false, true), std::make_unique<LbryChannelPage>(program, title, url), create_search_bar("Search...", 500) });
+ return PluginResult::OK;
+ }
+
+ SearchResult LbryChannelPage::search(const std::string &str, BodyItems &result_items) {
+ return plugin_result_to_search_result(get_page(str, 0, result_items));
+ }
+
+ PluginResult LbryChannelPage::get_page(const std::string &str, int page, BodyItems &result_items) {
+ if(!str.empty())
+ return search_page.get_page(str, page, result_items);
+
+ const int64_t time_now = time(nullptr);
+
+ Json::Value channel_ids_json(Json::arrayValue);
+ channel_ids_json.append(channel_id);
+
+ Json::Value claim_type_json(Json::arrayValue);
+ claim_type_json.append("stream");
+ claim_type_json.append("repost");
+
+ Json::Value not_tags_json(Json::arrayValue);
+ not_tags_json.append("porn");
+ not_tags_json.append("porno");
+ not_tags_json.append("nsfw");
+ not_tags_json.append("mature");
+ not_tags_json.append("xxx");
+ not_tags_json.append("sex");
+ not_tags_json.append("creampie");
+ not_tags_json.append("blowjob");
+ not_tags_json.append("handjob");
+ not_tags_json.append("vagina");
+ not_tags_json.append("boobs");
+ not_tags_json.append("big boobs");
+ not_tags_json.append("big dick");
+ not_tags_json.append("pussy");
+ not_tags_json.append("cumshot");
+ not_tags_json.append("anal");
+ not_tags_json.append("hard fucking");
+ not_tags_json.append("ass");
+ not_tags_json.append("fuck");
+ not_tags_json.append("hentai");
+
+ Json::Value order_by_json(Json::arrayValue);
+ order_by_json.append("release_time");
+
+ Json::Value request_params_json(Json::objectValue);
+ request_params_json["channel_ids"] = std::move(channel_ids_json);
+ request_params_json["claim_type"] = std::move(claim_type_json);
+ request_params_json["fee_amount"] = ">=0";
+ request_params_json["has_source"] = true;
+ request_params_json["include_purchase_receipt"] = true;
+ request_params_json["no_totals"] = true;
+ request_params_json["not_tags"] = std::move(not_tags_json);
+ request_params_json["order_by"] = std::move(order_by_json);
+ request_params_json["page"] = 1 + page;
+ request_params_json["page_size"] = 20;
+ request_params_json["release_time"] = "<" + std::to_string(time_now);
+
+ Json::Value request_json(Json::objectValue);
+ request_json["id"] = time_now * 1000;
+ request_json["jsonrpc"] = "2.0";
+ request_json["method"] = "claim_search";
+ request_json["params"] = std::move(request_params_json);
+ return resolve_claims(this, request_json, result_items);
+ }
+
+ PluginResult LbryChannelPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back(Tab{ nullptr, std::make_unique<LbryVideoPage>(program, title, url), nullptr });
+ return PluginResult::OK;
+ }
+
+ PluginResult LbryChannelPage::lazy_fetch(BodyItems &result_items) {
+ return get_page("", 0, result_items);
+ }
+
+ static PluginResult video_get_stream_url(Page *page, const std::string &video_url, std::string &streaming_url, std::string &err_str) {
+ std::string url = "https://api.na-backend.odysee.com/api/v1/proxy?m=resolve";
+
+ Json::Value request_params_json(Json::objectValue);
+ request_params_json["save_file"] = false;
+ request_params_json["uri"] = video_url;
+
+ Json::Value request_json(Json::objectValue);
+ request_json["id"] = (int64_t)time(nullptr) * 1000;
+ request_json["jsonrpc"] = "2.0";
+ request_json["method"] = "get";
+ request_json["params"] = std::move(request_params_json);
+
+ Json::StreamWriterBuilder json_builder;
+ json_builder["commentStyle"] = "None";
+ json_builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", Json::writeString(json_builder, request_json) }
+ };
+
+ Json::Value json_root;
+ DownloadResult download_result = page->download_json(json_root, url, std::move(additional_args), true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ err_str.clear();
+ if(handle_error(json_root, err_str))
+ return PluginResult::ERR;
+
+ const Json::Value &result_json = json_root["result"];
+ if(!result_json.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &streaming_url_json = result_json["streaming_url"];
+ if(!streaming_url_json.isString())
+ return PluginResult::ERR;
+
+ streaming_url = streaming_url_json.asString();
+ return PluginResult::OK;
+ }
+
+ std::unique_ptr<Page> LbryVideoPage::create_comments_page(Program*) {
+ return nullptr;
+ }
+
+ std::unique_ptr<RelatedVideosPage> LbryVideoPage::create_related_videos_page(Program*) {
+ return nullptr;
+ }
+
+ std::unique_ptr<Page> LbryVideoPage::create_channels_page(Program*, const std::string&) {
+ return nullptr;
+ }
+
+ // TODO: Support |max_height|. This can be done by gettin video source hash and checking for sd_hash and then resolution.
+ // If max_height is below max resolution height then choose the sd_hash version (replace hash in video stream with sd hash for the lower quality version)
+ std::string LbryVideoPage::get_download_url(int max_height) {
+ bool has_embedded_audio;
+ std::string ext;
+ return get_video_url(max_height, has_embedded_audio, ext);
+ }
+
+ std::string LbryVideoPage::get_video_url(int max_height, bool &has_embedded_audio, std::string &ext) {
+ has_embedded_audio = true;
+ ext = ".mp4"; // TODO: Check if this is always correct
+ return streaming_url;
+ }
+
+ std::string LbryVideoPage::get_audio_url(std::string&) {
+ return "";
+ }
+
+ PluginResult LbryVideoPage::load(std::string &title, std::string&, std::vector<MediaChapter>&, std::string &err_str) {
+ streaming_url.clear();
+ title = this->title;
+ return video_get_stream_url(this, url, streaming_url, err_str);
+ }
+} \ No newline at end of file
diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp
index df86207..b1e61c4 100644
--- a/src/plugins/Manganelo.cpp
+++ b/src/plugins/Manganelo.cpp
@@ -109,7 +109,7 @@ namespace QuickMedia {
std::string url = "https://manganelo.com/getstorysearchjson";
std::string search_term = "searchword=";
search_term += url_param_encode(str);
- CommandArg data_arg = { "--data", std::move(search_term) };
+ CommandArg data_arg = { "--form-string", std::move(search_term) };
Json::Value json_root;
DownloadResult result = download_json(json_root, url, {data_arg}, true);
diff --git a/src/plugins/Peertube.cpp b/src/plugins/Peertube.cpp
index 3b74871..b83daf5 100644
--- a/src/plugins/Peertube.cpp
+++ b/src/plugins/Peertube.cpp
@@ -56,28 +56,6 @@ namespace QuickMedia {
return plugin_result_to_search_result(get_page(str, 0, result_items));
}
- static std::string seconds_to_duration(int seconds) {
- seconds = std::max(0, seconds);
-
- int minutes = seconds / 60;
- int hours = minutes / 60;
- char buffer[32];
-
- if(hours >= 1) {
- minutes -= (hours * 60);
- seconds -= (hours * 60 * 60);
- seconds -= (minutes * 60);
- snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d", hours, minutes, seconds);
- } else if(minutes >= 1) {
- seconds -= (minutes * 60);
- snprintf(buffer, sizeof(buffer), "%02d:%02d", minutes, seconds);
- } else {
- snprintf(buffer, sizeof(buffer), "0:%02d", seconds);
- }
-
- return buffer;
- }
-
// TODO: Support remote content
static std::shared_ptr<BodyItem> search_data_to_body_item(const Json::Value &data_json, const std::string &server, PeertubeSearchPage::SearchType search_type) {
if(!data_json.isObject())
diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp
index d54060d..8885dfb 100644
--- a/src/plugins/Soundcloud.cpp
+++ b/src/plugins/Soundcloud.cpp
@@ -27,42 +27,14 @@ namespace QuickMedia {
return "";
}
- static std::string duration_to_descriptive_string(int64_t milliseconds) {
- time_t seconds = milliseconds / 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");
- minutes -= (hours * 60);
- 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());
+ return seconds_to_duration(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 seconds_to_duration(duration_json.asInt64());
return "";
}