diff options
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | plugins/Youtube.hpp | 16 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 11 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 74 |
4 files changed, 86 insertions, 17 deletions
@@ -128,7 +128,6 @@ Ctrl+F to either bring up search that searches in the body (filtering, of search Or do not have such an overlay and instead just put the autocomplete list below the search bar and close the autocomplete when pressing ESC or clicking inside the body or moving to another tab. Automatically delete old thumbnails and media files. Ctrl+I for youtube comments that have a timestamp should have an option to jump to the timestamp in the video. Also do the same for the youtube video description. -Ctrl+R for youtube should have a page for the video title, description, views, upload date, likes/dislikes (and maybe a button to go to the channel instead of having a channels tab?). Add a keybinding for going to the front page of the plugin. This would be especially useful for youtube and manga, where you have history, subscriptions and recommendations. Reaching end of download menu filename entry should scroll the entry vertically instead of putting the text on multiple lines. Ctrl+S saving a video should copy the video from cache if the video was downloaded to cache, instead of downloading it again. @@ -205,3 +204,4 @@ Allow specifying start/end range for video/music downloads. Limit text input length for 4chan posts to the server limit. Allow creating a new thread on 4chan. Add flag to quickmedia to use svp, by making svp use the same input ipc socket as quickmedia (and load the svp script manually). +Support directly going to a youtube channel for a url. This is helpful for opening channel urls directly with quickmedia and also going to another channel from a youtube description.
\ No newline at end of file diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp index 23f7e20..d3a57a6 100644 --- a/plugins/Youtube.hpp +++ b/plugins/Youtube.hpp @@ -23,6 +23,14 @@ namespace QuickMedia { YoutubeFormat base; }; + struct YoutubeVideoDetails { + std::string title; + std::string author; + std::string rating; + std::string views; + std::string description; + }; + // Returns |url| if the url is already a youtube url std::string invidious_url_to_youtube_url(const std::string &url); bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id); @@ -52,6 +60,12 @@ namespace QuickMedia { std::unordered_set<std::string> added_videos; }; + class YoutubeDescriptionPage : public Page { + public: + YoutubeDescriptionPage(Program *program) : Page(program) {} + const char* get_title() const override { return "Description"; } + }; + class YoutubeCommentsPage : public LazyFetchPage { public: YoutubeCommentsPage(Program *program, const std::string &video_url, const std::string &continuation_token) : LazyFetchPage(program), video_url(video_url), continuation_token(continuation_token) {} @@ -144,6 +158,7 @@ namespace QuickMedia { const char* get_title() const override { return ""; } BodyItems get_related_media(const std::string &url) override; PluginResult get_related_pages(const BodyItems &related_videos, const std::string &channel_url, std::vector<Tab> &result_tabs) override; + int get_related_pages_first_tab() override { return 1; } void set_url(std::string new_url) override; std::string get_url_timestamp() override { return timestamp; } std::string get_video_url(int max_height, bool &has_embedded_audio, std::string &ext) override; @@ -166,5 +181,6 @@ namespace QuickMedia { std::string playback_url; std::string watchtime_url; std::string tracking_url; + YoutubeVideoDetails video_details; }; } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 29976ad..a1320e0 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3082,17 +3082,19 @@ namespace QuickMedia { window.setMouseCursorVisible(true); cursor_visible = true; + XUnmapWindow(disp, video_player_window); + XSync(disp, False); + std::vector<Tab> related_pages; TaskResult related_pages_result = run_task_with_loading_screen([&video_page, &related_videos, &channel_url, &related_pages]{ return video_page->get_related_pages(related_videos, channel_url, related_pages) == PluginResult::OK; }); if(related_pages_result == TaskResult::FALSE) { + XMapWindow(disp, video_player_window); + XSync(disp, False); show_notification("QuickMedia", "Failed to get related pages", Urgency::CRITICAL); } else if(related_pages_result == TaskResult::TRUE) { - XUnmapWindow(disp, video_player_window); - XSync(disp, False); - bool page_changed = false; double resume_start_time = 0.0; page_loop(related_pages, video_page->get_related_pages_first_tab(), [&](const std::vector<Tab> &new_tabs) { @@ -3125,6 +3127,9 @@ namespace QuickMedia { XMapWindow(disp, video_player_window); XSync(disp, False); } + } else { + XMapWindow(disp, video_player_window); + XSync(disp, False); } } } else if(pressed_keysym == XK_c && pressing_ctrl) { diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index c7a9e5c..7a5f0d5 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -2167,9 +2167,46 @@ namespace QuickMedia { return result_items; } + static std::shared_ptr<BodyItem> video_details_to_body_item(const YoutubeVideoDetails &video_details) { + auto body_item = BodyItem::create(video_details.title); + + std::string description; + if(!video_details.views.empty()) { + description = video_details.views + " view" + (video_details.views == "1" ? "" : "s"); + } + + if(!video_details.rating.empty()) { + if(!description.empty()) + description += " • "; + description += "rated " + video_details.rating + "/5"; + } + + if(!video_details.author.empty()) { + if(!description.empty()) + description += '\n'; + description += video_details.author; + } + + if(!video_details.description.empty()) { + if(!description.empty()) + description += "\n\n"; + description += video_details.description; + } + + if(!description.empty()) + body_item->set_description(std::move(description)); + + return body_item; + } + PluginResult YoutubeVideoPage::get_related_pages(const BodyItems &related_videos, const std::string &channel_url, std::vector<Tab> &result_tabs) { + auto description_page_body = create_body(); + description_page_body->append_item(video_details_to_body_item(video_details)); + auto related_page_body = create_body(false, true); related_page_body->set_items(related_videos); + + result_tabs.push_back(Tab{std::move(description_page_body), std::make_unique<YoutubeDescriptionPage>(program), nullptr}); result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeCommentsPage>(program, url, comments_continuation_token), nullptr}); result_tabs.push_back(Tab{std::move(related_page_body), std::make_unique<YoutubeRelatedVideosPage>(program), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); result_tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeChannelPage>(program, channel_url, "", "Channel videos"), create_search_bar("Search...", 350)}); @@ -2342,6 +2379,14 @@ namespace QuickMedia { } } + static void video_details_clear(YoutubeVideoDetails &video_details) { + video_details.title.clear(); + video_details.author.clear(); + video_details.rating.clear(); + video_details.views.clear(); + video_details.description.clear(); + } + PluginResult YoutubeVideoPage::parse_video_response(const Json::Value &json_root, std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::string &err_str) { livestream_url.clear(); video_formats.clear(); @@ -2350,6 +2395,7 @@ namespace QuickMedia { title.clear(); channel_url.clear(); chapters.clear(); + video_details_clear(video_details); if(!json_root.isObject()) return PluginResult::ERR; @@ -2392,19 +2438,25 @@ namespace QuickMedia { const Json::Value &video_details_json = json_root["videoDetails"]; if(video_details_json.isObject()) { - const Json::Value &title_json = video_details_json["title"]; - if(title_json.isString()) - title = title_json.asString(); - const Json::Value &channel_id_json = video_details_json["channelId"]; if(channel_id_json.isString()) channel_url = "https://www.youtube.com/channel/" + channel_id_json.asString(); - const Json::Value &description_json = video_details_json["shortDescription"]; - if(description_json.isString()) { - std::string description = description_json.asString(); - chapters = youtube_description_extract_chapters(description); - } + const Json::Value &title_json = video_details_json["title"]; + const Json::Value &author_json = video_details_json["author"]; + const Json::Value &average_rating_json = video_details_json["averageRating"]; + const Json::Value &view_count_json = video_details_json["viewCount"]; + const Json::Value &short_description_json = video_details_json["shortDescription"]; + + if(title_json.isString()) video_details.title = title_json.asString(); + if(author_json.isString()) video_details.author = author_json.asString(); + if(average_rating_json.isDouble()) video_details.rating = std::to_string(average_rating_json.asDouble()); + if(view_count_json.isString()) video_details.views = view_count_json.asString(); + if(short_description_json.isString()) video_details.description = short_description_json.asString(); + + title = video_details.title; + if(!video_details.description.empty()) + chapters = youtube_description_extract_chapters(video_details.description); } const Json::Value &captions_json = json_root["captions"]; @@ -2456,10 +2508,6 @@ namespace QuickMedia { } PluginResult YoutubeVideoPage::load(std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::string &err_str) { - livestream_url.clear(); - video_formats.clear(); - audio_formats.clear(); - std::string video_id; if(!youtube_url_extract_id(url, video_id)) { fprintf(stderr, "Failed to extract youtube id from %s\n", url.c_str()); |